Skip to main content

hotshot_query_service/data_source/storage/sql/queries/
state.rs

1// Copyright (c) 2022 Espresso Systems (espressosys.com)
2// This file is part of the HotShot Query Service library.
3//
4// This program is free software: you can redistribute it and/or modify it under the terms of the GNU
5// General Public License as published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
8// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9// General Public License for more details.
10// You should have received a copy of the GNU General Public License along with this program. If not,
11// see <https://www.gnu.org/licenses/>.
12
13//! Merklized state storage implementation for a database query engine.
14
15use std::{
16    collections::{HashMap, HashSet, VecDeque},
17    sync::Arc,
18};
19
20#[cfg(not(feature = "embedded-db"))]
21use anyhow::Context;
22use ark_serialize::CanonicalDeserialize;
23use async_trait::async_trait;
24use futures::stream::TryStreamExt;
25use hotshot_types::traits::node_implementation::NodeType;
26use jf_merkle_tree_compat::{
27    DigestAlgorithm, MerkleCommitment, ToTraversalPath,
28    prelude::{MerkleNode, MerkleProof},
29};
30use sqlx::types::{BitVec, JsonValue};
31
32use super::{
33    super::transaction::{Transaction, TransactionMode, Write, query_as},
34    DecodeError, QueryBuilder,
35};
36#[cfg(feature = "embedded-db")]
37use crate::data_source::storage::sql::build_where_in;
38use crate::{
39    QueryError, QueryResult,
40    data_source::storage::{
41        MerklizedStateHeightStorage, MerklizedStateStorage, pruning::PrunedHeightStorage,
42        sql::sqlx::Row,
43    },
44    merklized_state::{MerklizedState, Snapshot},
45};
46
47#[async_trait]
48impl<Mode, Types, State, const ARITY: usize> MerklizedStateStorage<Types, State, ARITY>
49    for Transaction<Mode>
50where
51    Mode: TransactionMode,
52    Types: NodeType,
53    State: MerklizedState<Types, ARITY> + 'static,
54{
55    /// Retrieves a Merkle path from the database
56    async fn get_path(
57        &mut self,
58        snapshot: Snapshot<Types, State, ARITY>,
59        key: State::Key,
60    ) -> QueryResult<MerkleProof<State::Entry, State::Key, State::T, ARITY>> {
61        let state_type = State::state_type();
62        let tree_height = State::tree_height();
63
64        // Get the traversal path of the index
65        let traversal_path = State::Key::to_traversal_path(&key, tree_height);
66        let (created, merkle_commitment) = self.snapshot_info(snapshot).await?;
67
68        // Get all the nodes in the path to the index.
69        // Order by pos DESC is to return nodes from the leaf to the root
70        let (query, sql) = build_get_path_query(state_type, traversal_path.clone(), created)?;
71        let rows = query.query(&sql).fetch_all(self.as_mut()).await?;
72
73        let nodes: Vec<Node> = rows.into_iter().map(|r| r.into()).collect();
74
75        // On postgres, the backfill moves Merkle rows from the legacy table into the `*_bigint`
76        // table by `created` (block height), low heights first. Mid-migration a single path's
77        // versions are split across both tables, so the latest version <= `created` for a path may
78        // live in either one. Query the legacy table for every path and keep, per path, the row
79        // with the greatest `created` across both tables. (Once the contract phase drops the legacy
80        // table and renames `*_bigint`, `state_type` no longer ends in `_bigint` and this is skipped.)
81        #[cfg(not(feature = "embedded-db"))]
82        let nodes = if let Some(legacy_table) = state_type.strip_suffix("_bigint") {
83            let mut query = QueryBuilder::default();
84            let paths_param = query.bind(traversal_path_values(&traversal_path, tree_height))?;
85            let created_param = query.bind(created)?;
86            let sql = format!(
87                "SELECT n.path, n.created, n.hash_id::BIGINT AS hash_id, n.children, \
88                 n.children_bitvec, n.idx, n.entry FROM unnest({paths_param}::jsonb[]) AS \
89                 p(path), LATERAL (SELECT * FROM {legacy_table} WHERE {legacy_table}.path = \
90                 p.path AND {legacy_table}.created <= {created_param} ORDER BY \
91                 {legacy_table}.path DESC, {legacy_table}.created DESC LIMIT 1) AS n"
92            );
93            let legacy_rows = query
94                .query(&sql)
95                .fetch_all(self.as_mut())
96                .await
97                .map_err(|e| QueryError::Error {
98                    message: format!("merkle path fallback lookup failed: {e}"),
99                })?;
100
101            let mut latest: HashMap<String, Node> = HashMap::new();
102            for node in nodes
103                .into_iter()
104                .chain(legacy_rows.into_iter().map(Node::from))
105            {
106                let key = node.path.to_string();
107                if latest
108                    .get(&key)
109                    .is_none_or(|cur| node.created > cur.created)
110                {
111                    latest.insert(key, node);
112                }
113            }
114            let mut nodes: Vec<Node> = latest.into_values().collect();
115            // Sort leaf-first (longer paths first) for proof reconstruction.
116            nodes.sort_by_key(|n| {
117                std::cmp::Reverse(n.path.as_array().map(|v| v.len()).unwrap_or(0))
118            });
119            nodes
120        } else {
121            nodes
122        };
123
124        // insert all the hash ids to a hashset which is used to query later
125        // HashSet is used to avoid duplicates
126        let mut hash_ids = HashSet::new();
127        for node in nodes.iter() {
128            hash_ids.insert(node.hash_id);
129            if let Some(children) = &node.children {
130                let children: Vec<i64> =
131                    serde_json::from_value(children.clone()).map_err(|e| QueryError::Error {
132                        message: format!("Error deserializing 'children' into Vec<i64>: {e}"),
133                    })?;
134                hash_ids.extend(children);
135            }
136        }
137
138        // Find all the hash values and create a hashmap
139        // Hashmap will be used to get the hash value of the nodes children and the node itself.
140        let hashes: HashMap<i64, Vec<u8>> = if !hash_ids.is_empty() {
141            #[cfg(not(feature = "embedded-db"))]
142            {
143                let hash_ids_arr: Vec<i64> = hash_ids.iter().copied().collect();
144                let mut result: HashMap<i64, Vec<u8>> = sqlx::query_as(
145                    "SELECT id::BIGINT, value FROM hash_bigint WHERE id = ANY($1::BIGINT[])",
146                )
147                .bind(&hash_ids_arr)
148                .fetch_all(self.as_mut())
149                .await
150                .map_err(|e| QueryError::Error {
151                    message: format!("hash lookup failed: {e}"),
152                })?
153                .into_iter()
154                .collect();
155
156                let missing: Vec<i64> = hash_ids
157                    .iter()
158                    .filter(|id| !result.contains_key(id))
159                    .copied()
160                    .collect();
161
162                if !missing.is_empty() {
163                    let rows: Vec<(i64, Vec<u8>)> = sqlx::query_as(
164                        "SELECT id::BIGINT, value FROM hash WHERE id = ANY($1::BIGINT[])",
165                    )
166                    .bind(&missing)
167                    .fetch_all(self.as_mut())
168                    .await
169                    .map_err(|e| QueryError::Error {
170                        message: format!("hash fallback lookup failed: {e}"),
171                    })?;
172                    result.extend(rows);
173                }
174                result
175            }
176
177            #[cfg(feature = "embedded-db")]
178            {
179                let (query, sql) =
180                    build_where_in("SELECT id, value FROM hash_bigint", "id", hash_ids)?;
181                query
182                    .query_as(&sql)
183                    .fetch(self.as_mut())
184                    .try_collect::<HashMap<i64, Vec<u8>>>()
185                    .await?
186            }
187        } else {
188            HashMap::new()
189        };
190
191        let mut proof_path = VecDeque::with_capacity(State::tree_height());
192        for Node {
193            hash_id,
194            children,
195            children_bitvec,
196            idx,
197            entry,
198            ..
199        } in nodes.iter()
200        {
201            {
202                let value = hashes.get(hash_id).ok_or(QueryError::Error {
203                    message: format!("node's value references non-existent hash {hash_id}"),
204                })?;
205
206                match (children, children_bitvec, idx, entry) {
207                    // If the row has children then its a branch
208                    (Some(children), Some(children_bitvec), None, None) => {
209                        let children: Vec<i64> =
210                            serde_json::from_value(children.clone()).map_err(|e| {
211                                QueryError::Error {
212                                    message: format!(
213                                        "Error deserializing 'children' into Vec<i64>: {e}"
214                                    ),
215                                }
216                            })?;
217                        let mut children = children.iter();
218
219                        // Reconstruct the Children MerkleNodes from storage.
220                        // Children bit_vec is used to create forgotten  or empty node
221                        let child_nodes = children_bitvec
222                            .iter()
223                            .map(|bit| {
224                                if bit {
225                                    let hash_id = children.next().ok_or(QueryError::Error {
226                                        message: "node has fewer children than set bits".into(),
227                                    })?;
228                                    let value = hashes.get(hash_id).ok_or(QueryError::Error {
229                                        message: format!(
230                                            "node's child references non-existent hash {hash_id}"
231                                        ),
232                                    })?;
233                                    Ok(Arc::new(MerkleNode::ForgettenSubtree {
234                                        value: State::T::deserialize_compressed(value.as_slice())
235                                            .decode_error("malformed merkle node value")?,
236                                    }))
237                                } else {
238                                    Ok(Arc::new(MerkleNode::Empty))
239                                }
240                            })
241                            .collect::<QueryResult<Vec<_>>>()?;
242                        // Use the Children merkle nodes to reconstruct the branch node
243                        proof_path.push_back(MerkleNode::Branch {
244                            value: State::T::deserialize_compressed(value.as_slice())
245                                .decode_error("malformed merkle node value")?,
246                            children: child_nodes,
247                        });
248                    },
249                    // If it has an entry, it's a leaf
250                    (None, None, Some(index), Some(entry)) => {
251                        proof_path.push_back(MerkleNode::Leaf {
252                            value: State::T::deserialize_compressed(value.as_slice())
253                                .decode_error("malformed merkle node value")?,
254                            pos: serde_json::from_value(index.clone())
255                                .decode_error("malformed merkle node index")?,
256                            elem: serde_json::from_value(entry.clone())
257                                .decode_error("malformed merkle element")?,
258                        });
259                    },
260                    // Otherwise, it's empty.
261                    (None, None, Some(_), None) => {
262                        proof_path.push_back(MerkleNode::Empty);
263                    },
264                    _ => {
265                        return Err(QueryError::Error {
266                            message: "Invalid type of merkle node found".to_string(),
267                        });
268                    },
269                }
270            }
271        }
272
273        // Reconstruct the merkle commitment from the path
274        let init = if let Some(MerkleNode::Leaf { value, .. }) = proof_path.front() {
275            *value
276        } else {
277            // If the path ends in a branch (or, as a special case, if the path and thus the entire
278            // tree is empty), we are looking up an entry that is not present in the tree. We always
279            // store all the nodes on all the paths to all the entries in the tree, so the only
280            // nodes we could be missing are empty nodes from unseen entries. Thus, we can
281            // reconstruct what the path should be by prepending empty nodes.
282            while proof_path.len() <= State::tree_height() {
283                proof_path.push_front(MerkleNode::Empty);
284            }
285            State::T::default()
286        };
287        let commitment_from_path = traversal_path
288            .iter()
289            .zip(proof_path.iter().skip(1))
290            .try_fold(init, |val, (branch, node)| -> QueryResult<State::T> {
291                match node {
292                    MerkleNode::Branch { value: _, children } => {
293                        let data = children
294                            .iter()
295                            .map(|node| match node.as_ref() {
296                                MerkleNode::ForgettenSubtree { value } => Ok(*value),
297                                MerkleNode::Empty => Ok(State::T::default()),
298                                _ => Err(QueryError::Error {
299                                    message: "Invalid child node".to_string(),
300                                }),
301                            })
302                            .collect::<QueryResult<Vec<_>>>()?;
303
304                        if data[*branch] != val {
305                            // This can only happen if data is missing: we have an old version of
306                            // one of the nodes in the path, which is why it is not matching up with
307                            // its parent.
308                            tracing::warn!(
309                                ?key,
310                                parent = ?data[*branch],
311                                child = ?val,
312                                branch = %*branch,
313                                %created,
314                                %merkle_commitment,
315                                "missing data in merklized state; parent-child mismatch",
316                            );
317                            return Err(QueryError::Missing);
318                        }
319
320                        State::Digest::digest(&data).map_err(|err| QueryError::Error {
321                            message: format!("failed to update digest: {err:#}"),
322                        })
323                    },
324                    MerkleNode::Empty => Ok(init),
325                    _ => Err(QueryError::Error {
326                        message: "Invalid type of Node in the proof".to_string(),
327                    }),
328                }
329            })?;
330
331        if commitment_from_path != merkle_commitment.digest() {
332            return Err(QueryError::Error {
333                message: format!(
334                    "Commitment calculated from merkle path ({commitment_from_path:?}) does not \
335                     match the commitment in the header ({:?})",
336                    merkle_commitment.digest()
337                ),
338            });
339        }
340
341        Ok(MerkleProof {
342            pos: key,
343            proof: proof_path.into(),
344        })
345    }
346}
347
348#[async_trait]
349impl<Mode: TransactionMode> MerklizedStateHeightStorage for Transaction<Mode> {
350    async fn get_last_state_height(&mut self) -> QueryResult<usize> {
351        let Some((height,)) = query_as::<(i64,)>("SELECT height from last_merklized_state_height")
352            .fetch_optional(self.as_mut())
353            .await?
354        else {
355            return Ok(0);
356        };
357        Ok(height as usize)
358    }
359}
360
361impl<Mode: TransactionMode> Transaction<Mode> {
362    /// Get information identifying a [`Snapshot`].
363    ///
364    /// If the given snapshot is known to the database, this function returns
365    /// * The block height at which the snapshot was created
366    /// * A digest of the Merkle commitment to the snapshotted state
367    async fn snapshot_info<Types, State, const ARITY: usize>(
368        &mut self,
369        snapshot: Snapshot<Types, State, ARITY>,
370    ) -> QueryResult<(i64, State::Commit)>
371    where
372        Types: NodeType,
373        State: MerklizedState<Types, ARITY>,
374    {
375        let header_state_commitment_field = State::header_state_commitment_field();
376
377        let (created, commit) = match snapshot {
378            Snapshot::Commit(commit) => {
379                // Get the block height using the merkle commitment. It is possible that multiple
380                // headers will have the same state commitment. In this case we don't care which
381                // height we get, since any query against equivalent states will yield equivalent
382                // results, regardless of which block the state is from. Thus, we can make this
383                // query fast with `LIMIT 1` and no `ORDER BY`.
384                let (height,) = query_as(&format!(
385                    "SELECT height
386                       FROM header
387                      WHERE {header_state_commitment_field} = $1
388                      LIMIT 1"
389                ))
390                .bind(commit.to_string())
391                .fetch_one(self.as_mut())
392                .await?;
393
394                (height, commit)
395            },
396            Snapshot::Index(created) => {
397                let created = created as i64;
398                let (commit,) = query_as::<(String,)>(&format!(
399                    "SELECT {header_state_commitment_field} AS root_commitment
400                       FROM header
401                      WHERE height = $1
402                      LIMIT 1"
403                ))
404                .bind(created)
405                .fetch_one(self.as_mut())
406                .await?;
407                let commit = serde_json::from_value(commit.into())
408                    .decode_error("malformed state commitment")?;
409                (created, commit)
410            },
411        };
412
413        // Make sure the requested snapshot is up to date.
414        let height = self.get_last_state_height().await?;
415
416        if height < (created as usize) {
417            return Err(QueryError::NotFound);
418        }
419
420        let pruned_height =
421            self.load_state_pruned_height()
422                .await
423                .map_err(|e| QueryError::Error {
424                    message: format!("failed to load pruned height: {e}"),
425                })?;
426
427        if pruned_height.is_some_and(|h| created <= h as i64) {
428            return Err(QueryError::NotFound);
429        }
430
431        Ok((created, commit))
432    }
433}
434
435// TODO: create a generic upsert function with retries that returns the column
436#[cfg(feature = "embedded-db")]
437pub(crate) fn build_hash_batch_insert(
438    hashes: &[Vec<u8>],
439) -> QueryResult<(QueryBuilder<'_>, String)> {
440    let mut query = QueryBuilder::default();
441    let params = hashes
442        .iter()
443        .map(|hash| Ok(format!("({})", query.bind(hash)?)))
444        .collect::<QueryResult<Vec<String>>>()?;
445    let sql = format!(
446        "INSERT INTO hash_bigint(value) values {} ON CONFLICT (value) DO UPDATE SET value = \
447         EXCLUDED.value returning value, id",
448        params.join(",")
449    );
450    Ok((query, sql))
451}
452
453/// Batch insert hashes using UNNEST for large batches (postgres only).
454/// Returns a map from hash bytes to their database IDs.
455#[cfg(not(feature = "embedded-db"))]
456pub(crate) async fn batch_insert_hashes(
457    hashes: Vec<Vec<u8>>,
458    tx: &mut Transaction<Write>,
459) -> QueryResult<HashMap<Vec<u8>, i64>> {
460    if hashes.is_empty() {
461        return Ok(HashMap::new());
462    }
463
464    // Use UNNEST-based batch insert (more efficient and avoids parameter limits).
465    // Cast id to BIGINT in RETURNING so the result maps directly to i64.
466    let sql = "INSERT INTO hash_bigint(value) SELECT * FROM UNNEST($1::bytea[]) ON CONFLICT \
467               (value) DO UPDATE SET value = EXCLUDED.value RETURNING value, id::BIGINT";
468
469    let result: HashMap<Vec<u8>, i64> = sqlx::query_as(sql)
470        .bind(&hashes)
471        .fetch(tx.as_mut())
472        .try_collect()
473        .await
474        .map_err(|e| QueryError::Error {
475            message: format!("batch hash insert failed: {e}"),
476        })?;
477
478    Ok(result)
479}
480
481/// Type alias for a merkle proof with its traversal path.
482pub(crate) type ProofWithPath<Entry, Key, T, const ARITY: usize> =
483    (MerkleProof<Entry, Key, T, ARITY>, Vec<usize>);
484
485/// Collects nodes and hashes from merkle proofs.
486/// Returns (nodes, hashes) for batch insertion.
487pub(crate) fn collect_nodes_from_proofs<Entry, Key, T, const ARITY: usize>(
488    proofs: &[ProofWithPath<Entry, Key, T, ARITY>],
489) -> QueryResult<(Vec<NodeWithHashes>, HashSet<Vec<u8>>)>
490where
491    Entry: jf_merkle_tree_compat::Element + serde::Serialize,
492    Key: jf_merkle_tree_compat::Index + serde::Serialize,
493    T: jf_merkle_tree_compat::NodeValue,
494{
495    let mut nodes = Vec::new();
496    let mut hashes = HashSet::new();
497
498    for (proof, traversal_path) in proofs {
499        let pos = &proof.pos;
500        let path = &proof.proof;
501        let mut trav_path = traversal_path.iter().map(|n| *n as i32);
502
503        for node in path.iter() {
504            match node {
505                MerkleNode::Empty => {
506                    let index =
507                        serde_json::to_value(pos.clone()).map_err(|e| QueryError::Error {
508                            message: format!("malformed merkle position: {e}"),
509                        })?;
510                    let node_path: Vec<i32> = trav_path.clone().rev().collect();
511                    nodes.push((
512                        Node {
513                            path: node_path.into(),
514                            idx: Some(index),
515                            ..Default::default()
516                        },
517                        None,
518                        [0_u8; 32].to_vec(),
519                    ));
520                    hashes.insert([0_u8; 32].to_vec());
521                },
522                MerkleNode::ForgettenSubtree { .. } => {
523                    return Err(QueryError::Error {
524                        message: "Node in the Merkle path contains a forgotten subtree".into(),
525                    });
526                },
527                MerkleNode::Leaf { value, pos, elem } => {
528                    let mut leaf_commit = Vec::new();
529                    value.serialize_compressed(&mut leaf_commit).map_err(|e| {
530                        QueryError::Error {
531                            message: format!("malformed merkle leaf commitment: {e}"),
532                        }
533                    })?;
534
535                    let node_path: Vec<i32> = trav_path.clone().rev().collect();
536
537                    let index =
538                        serde_json::to_value(pos.clone()).map_err(|e| QueryError::Error {
539                            message: format!("malformed merkle position: {e}"),
540                        })?;
541                    let entry = serde_json::to_value(elem).map_err(|e| QueryError::Error {
542                        message: format!("malformed merkle element: {e}"),
543                    })?;
544
545                    nodes.push((
546                        Node {
547                            path: node_path.into(),
548                            idx: Some(index),
549                            entry: Some(entry),
550                            ..Default::default()
551                        },
552                        None,
553                        leaf_commit.clone(),
554                    ));
555
556                    hashes.insert(leaf_commit);
557                },
558                MerkleNode::Branch { value, children } => {
559                    let mut branch_hash = Vec::new();
560                    value.serialize_compressed(&mut branch_hash).map_err(|e| {
561                        QueryError::Error {
562                            message: format!("malformed merkle branch hash: {e}"),
563                        }
564                    })?;
565
566                    let mut children_bitvec = BitVec::new();
567                    let mut children_values = Vec::new();
568                    for child in children {
569                        let child = child.as_ref();
570                        match child {
571                            MerkleNode::Empty => {
572                                children_bitvec.push(false);
573                            },
574                            MerkleNode::Branch { value, .. }
575                            | MerkleNode::Leaf { value, .. }
576                            | MerkleNode::ForgettenSubtree { value } => {
577                                let mut hash = Vec::new();
578                                value.serialize_compressed(&mut hash).map_err(|e| {
579                                    QueryError::Error {
580                                        message: format!("malformed merkle node hash: {e}"),
581                                    }
582                                })?;
583
584                                children_values.push(hash);
585                                children_bitvec.push(true);
586                            },
587                        }
588                    }
589
590                    let node_path: Vec<i32> = trav_path.clone().rev().collect();
591                    nodes.push((
592                        Node {
593                            path: node_path.into(),
594                            children: None,
595                            children_bitvec: Some(children_bitvec),
596                            ..Default::default()
597                        },
598                        Some(children_values.clone()),
599                        branch_hash.clone(),
600                    ));
601                    hashes.insert(branch_hash);
602                    hashes.extend(children_values);
603                },
604            }
605
606            trav_path.next();
607        }
608    }
609
610    Ok((nodes, hashes))
611}
612
613// Represents a row in a state table
614#[derive(Debug, Default, Clone)]
615pub(crate) struct Node {
616    pub(crate) path: JsonValue,
617    pub(crate) created: i64,
618    pub(crate) hash_id: i64,
619    pub(crate) children: Option<JsonValue>,
620    pub(crate) children_bitvec: Option<BitVec>,
621    pub(crate) idx: Option<JsonValue>,
622    pub(crate) entry: Option<JsonValue>,
623}
624
625/// Type alias for node data with optional children hashes and node hash.
626/// Used during batch collection before database insertion.
627pub(crate) type NodeWithHashes = (Node, Option<Vec<Vec<u8>>>, Vec<u8>);
628
629#[cfg(feature = "embedded-db")]
630impl From<sqlx::sqlite::SqliteRow> for Node {
631    fn from(row: sqlx::sqlite::SqliteRow) -> Self {
632        let bit_string: Option<String> = row.get_unchecked("children_bitvec");
633        let children_bitvec: Option<BitVec> =
634            bit_string.map(|b| b.chars().map(|c| c == '1').collect());
635
636        Self {
637            path: row.get_unchecked("path"),
638            created: row.get_unchecked("created"),
639            hash_id: row.get_unchecked("hash_id"),
640            children: row.get_unchecked("children"),
641            children_bitvec,
642            idx: row.get_unchecked("idx"),
643            entry: row.get_unchecked("entry"),
644        }
645    }
646}
647
648#[cfg(not(feature = "embedded-db"))]
649impl From<sqlx::postgres::PgRow> for Node {
650    fn from(row: sqlx::postgres::PgRow) -> Self {
651        Self {
652            path: row.get_unchecked("path"),
653            created: row.get_unchecked("created"),
654            hash_id: row.get_unchecked("hash_id"),
655            children: row.get_unchecked("children"),
656            children_bitvec: row.get_unchecked("children_bitvec"),
657            idx: row.get_unchecked("idx"),
658            entry: row.get_unchecked("entry"),
659        }
660    }
661}
662
663impl Node {
664    pub(crate) async fn upsert(
665        name: &str,
666        nodes: impl IntoIterator<Item = Self>,
667        tx: &mut Transaction<Write>,
668    ) -> anyhow::Result<()> {
669        let nodes: Vec<_> = nodes.into_iter().collect();
670
671        // Use UNNEST-based batch insert for postgres (more efficient and avoids parameter limits)
672        #[cfg(not(feature = "embedded-db"))]
673        return Self::upsert_batch_unnest(name, nodes, tx).await;
674
675        #[cfg(feature = "embedded-db")]
676        {
677            for node_chunk in nodes.chunks(20) {
678                let rows: Vec<_> = node_chunk
679                    .iter()
680                    .map(|n| {
681                        let children_bitvec: Option<String> = n
682                            .children_bitvec
683                            .clone()
684                            .map(|b| b.iter().map(|bit| if bit { '1' } else { '0' }).collect());
685
686                        (
687                            n.path.clone(),
688                            n.created,
689                            n.hash_id,
690                            n.children.clone(),
691                            children_bitvec,
692                            n.idx.clone(),
693                            n.entry.clone(),
694                        )
695                    })
696                    .collect();
697
698                tx.upsert(
699                    name,
700                    [
701                        "path",
702                        "created",
703                        "hash_id",
704                        "children",
705                        "children_bitvec",
706                        "idx",
707                        "entry",
708                    ],
709                    ["path", "created"],
710                    rows,
711                )
712                .await?;
713            }
714            Ok(())
715        }
716    }
717
718    #[cfg(not(feature = "embedded-db"))]
719    async fn upsert_batch_unnest(
720        name: &str,
721        nodes: Vec<Self>,
722        tx: &mut Transaction<Write>,
723    ) -> anyhow::Result<()> {
724        if nodes.is_empty() {
725            return Ok(());
726        }
727
728        // Deduplicate nodes by (path, created) - keep the last occurrence
729        // This is required because UNNEST + ON CONFLICT cannot handle duplicates in the same batch
730        let mut deduped = HashMap::new();
731        for node in nodes {
732            deduped.insert((node.path.to_string(), node.created), node);
733        }
734
735        let mut paths = Vec::with_capacity(deduped.len());
736        let mut createds = Vec::with_capacity(deduped.len());
737        let mut hash_ids = Vec::with_capacity(deduped.len());
738        let mut childrens = Vec::with_capacity(deduped.len());
739        let mut children_bitvecs = Vec::with_capacity(deduped.len());
740        let mut idxs = Vec::with_capacity(deduped.len());
741        let mut entries = Vec::with_capacity(deduped.len());
742
743        for node in deduped.into_values() {
744            paths.push(node.path);
745            createds.push(node.created);
746            hash_ids.push(node.hash_id);
747            childrens.push(node.children);
748            children_bitvecs.push(node.children_bitvec);
749            idxs.push(node.idx);
750            entries.push(node.entry);
751        }
752
753        let sql = format!(
754            r#"
755            INSERT INTO "{name}" (path, created, hash_id, children, children_bitvec, idx, entry)
756            SELECT * FROM UNNEST($1::jsonb[], $2::bigint[], $3::bigint[], $4::jsonb[], $5::bit varying[], $6::jsonb[], $7::jsonb[])
757            ON CONFLICT (path, created) DO UPDATE SET
758                hash_id = EXCLUDED.hash_id,
759                children = EXCLUDED.children,
760                children_bitvec = EXCLUDED.children_bitvec,
761                idx = EXCLUDED.idx,
762                entry = EXCLUDED.entry
763            "#
764        );
765
766        sqlx::query(&sql)
767            .bind(&paths)
768            .bind(&createds)
769            .bind(&hash_ids)
770            .bind(&childrens)
771            .bind(&children_bitvecs)
772            .bind(&idxs)
773            .bind(&entries)
774            .execute(tx.as_mut())
775            .await
776            .context("batch upsert with UNNEST failed")?;
777
778        Ok(())
779    }
780}
781
782/// Compute the full set of path JSON values for a traversal path (leaf to root).
783///
784/// Each element is the path prefix used as a row key in a Merkle-tree table.
785fn traversal_path_values(traversal_path: &[usize], tree_height: usize) -> Vec<serde_json::Value> {
786    let len = tree_height;
787    let mut result = Vec::with_capacity(len + 1);
788    let mut trav = traversal_path.iter().map(|x| *x as i32);
789    for _ in 0..=len {
790        let path: Vec<i32> = trav.clone().rev().collect();
791        result.push(serde_json::Value::from(path));
792        trav.next();
793    }
794    result
795}
796
797fn build_get_path_query<'q>(
798    table: &'static str,
799    traversal_path: Vec<usize>,
800    created: i64,
801) -> QueryResult<(QueryBuilder<'q>, String)> {
802    let mut query = QueryBuilder::default();
803    let paths = traversal_path_values(&traversal_path, traversal_path.len());
804
805    #[cfg(not(feature = "embedded-db"))]
806    let sql = {
807        let paths_param = query.bind(paths)?;
808        let created_param = query.bind(created)?;
809        // One LATERAL point-lookup per path in the array, instead of N UNION'd subqueries.
810        // Postgres plans the inner subquery once and reuses it, collapsing N independent
811        // planner decisions into one.
812        format!(
813            "SELECT n.* FROM unnest({paths_param}::jsonb[]) AS p(path), LATERAL (SELECT * FROM \
814             {table} WHERE {table}.path = p.path AND {table}.created <= {created_param} ORDER BY \
815             {table}.path DESC, {table}.created DESC LIMIT 1) AS n ORDER BY n.path DESC"
816        )
817    };
818
819    #[cfg(feature = "embedded-db")]
820    let sql = {
821        // SQLite has no native array binding, so we keep the UNION form. Each subquery
822        // is a point lookup by the composite (path, created) primary key.
823        let created_param = query.bind(created)?;
824        let mut sub_queries = Vec::with_capacity(paths.len());
825        for path in paths {
826            let node_path = query.bind(path)?;
827            sub_queries.push(format!(
828                "SELECT * FROM (SELECT * FROM {table} WHERE path = {node_path} AND created <= \
829                 {created_param} ORDER BY path DESC, created DESC LIMIT 1) AS latest_node",
830            ));
831        }
832        format!(
833            "SELECT * FROM ({}) as t ORDER BY length(t.path) DESC",
834            sub_queries.join(" UNION ")
835        )
836    };
837
838    Ok((query, sql))
839}
840
841#[cfg(test)]
842mod test {
843    use futures::stream::StreamExt;
844    use jf_merkle_tree_compat::{
845        LookupResult, MerkleTreeScheme, UniversalMerkleTreeScheme,
846        universal_merkle_tree::UniversalMerkleTree,
847    };
848    use rand::{RngCore, seq::IteratorRandom};
849
850    use super::*;
851    use crate::{
852        data_source::{
853            VersionedDataSource,
854            storage::sql::{testing::TmpDb, *},
855        },
856        merklized_state::UpdateStateData,
857        testing::mocks::{MockMerkleTree, MockTypes},
858    };
859
860    #[test_log::test(tokio::test(flavor = "multi_thread"))]
861    async fn test_merklized_state_storage() {
862        // In this test we insert some entries into the tree and update the database
863        // Each entry's merkle path is compared with the path from the tree
864
865        let db = TmpDb::init().await;
866        let storage = SqlStorage::connect(db.config(), StorageConnectionType::Query)
867            .await
868            .unwrap();
869
870        // define a test tree
871        let mut test_tree: UniversalMerkleTree<_, _, _, 8, _> =
872            MockMerkleTree::new(MockMerkleTree::tree_height());
873        let block_height = 1;
874
875        // insert some entries into the tree and the header table
876        // Header table is used the get_path query to check if the header exists for the block height.
877        let mut tx = storage.write().await.unwrap();
878        for i in 0..27 {
879            test_tree.update(i, i).unwrap();
880
881            // data field of the header
882            let test_data = serde_json::json!({ MockMerkleTree::header_state_commitment_field() : serde_json::to_value(test_tree.commitment()).unwrap()});
883            tx.upsert(
884                "header",
885                [
886                    "height",
887                    "hash",
888                    "payload_hash",
889                    "timestamp",
890                    "ns_table",
891                    "data",
892                ],
893                ["height"],
894                [(
895                    block_height as i64,
896                    format!("randomHash{i}"),
897                    "t".to_string(),
898                    0,
899                    "ns_table".to_string(),
900                    test_data,
901                )],
902            )
903            .await
904            .unwrap();
905            // proof for the index from the tree
906            let (_, proof) = test_tree.lookup(i).expect_ok().unwrap();
907            // traversal path for the index.
908            let traversal_path =
909                <usize as ToTraversalPath<8>>::to_traversal_path(&i, test_tree.height());
910
911            UpdateStateData::<_, MockMerkleTree, 8>::insert_merkle_nodes(
912                &mut tx,
913                proof.clone(),
914                traversal_path.clone(),
915                block_height as u64,
916            )
917            .await
918            .expect("failed to insert nodes");
919        }
920        // update saved state height
921        UpdateStateData::<_, MockMerkleTree, 8>::set_last_state_height(&mut tx, block_height)
922            .await
923            .unwrap();
924        tx.commit().await.unwrap();
925
926        //Get the path and check if it matches the lookup
927        for i in 0..27 {
928            // Query the path for the index
929            let mut tx = storage.read().await.unwrap();
930            let merkle_path = tx
931                .get_path(
932                    Snapshot::<_, MockMerkleTree, 8>::Index(block_height as u64),
933                    i,
934                )
935                .await
936                .unwrap();
937
938            let (_, proof) = test_tree.lookup(i).expect_ok().unwrap();
939
940            tracing::info!("merkle path {:?}", merkle_path);
941
942            // merkle path from the storage should match the path from test tree
943            assert_eq!(merkle_path, proof.clone(), "merkle paths mismatch");
944        }
945
946        // Get the proof of index 0 with bh = 1
947        let (_, proof_bh_1) = test_tree.lookup(0).expect_ok().unwrap();
948        // Inserting Index 0 again with created (bh) = 2
949        // Our database should then have 2 versions of this leaf node
950        // Update the node so that proof is also updated
951        test_tree.update(0, 99).unwrap();
952        // Also update the merkle commitment in the header
953
954        // data field of the header
955        let mut tx = storage.write().await.unwrap();
956        let test_data = serde_json::json!({ MockMerkleTree::header_state_commitment_field() : serde_json::to_value(test_tree.commitment()).unwrap()});
957        tx.upsert(
958            "header",
959            [
960                "height",
961                "hash",
962                "payload_hash",
963                "timestamp",
964                "data",
965                "ns_table",
966            ],
967            ["height"],
968            [(
969                2i64,
970                "randomstring".to_string(),
971                "t".to_string(),
972                0,
973                test_data,
974                "ns_table".to_string(),
975            )],
976        )
977        .await
978        .unwrap();
979        let (_, proof_bh_2) = test_tree.lookup(0).expect_ok().unwrap();
980        // traversal path for the index.
981        let traversal_path =
982            <usize as ToTraversalPath<8>>::to_traversal_path(&0, test_tree.height());
983        // Update storage to insert a new version of this code
984
985        UpdateStateData::<_, MockMerkleTree, 8>::insert_merkle_nodes(
986            &mut tx,
987            proof_bh_2.clone(),
988            traversal_path.clone(),
989            2,
990        )
991        .await
992        .expect("failed to insert nodes");
993        // update saved state height
994        UpdateStateData::<_, MockMerkleTree, 8>::set_last_state_height(&mut tx, 2)
995            .await
996            .unwrap();
997        tx.commit().await.unwrap();
998
999        let node_path = traversal_path
1000            .into_iter()
1001            .rev()
1002            .map(|n| n as i32)
1003            .collect::<Vec<_>>();
1004
1005        // Find all the nodes of Index 0 in table
1006        let mut tx = storage.read().await.unwrap();
1007        let rows = query("SELECT * from test_tree where path = $1 ORDER BY created")
1008            .bind(serde_json::to_value(node_path).unwrap())
1009            .fetch(tx.as_mut());
1010
1011        let nodes: Vec<Node> = rows.map(|res| res.unwrap().into()).collect().await;
1012        // There should be only 2 versions of this node
1013        assert!(nodes.len() == 2, "incorrect number of nodes");
1014        assert_eq!(nodes[0].created, 1, "wrong block height");
1015        assert_eq!(nodes[1].created, 2, "wrong block height");
1016
1017        // Now we can have two snapshots for Index 0
1018        // One with created = 1 and other with 2
1019        // Query snapshot with created = 2
1020
1021        let path_with_bh_2 = storage
1022            .read()
1023            .await
1024            .unwrap()
1025            .get_path(Snapshot::<_, MockMerkleTree, 8>::Index(2), 0)
1026            .await
1027            .unwrap();
1028
1029        assert_eq!(path_with_bh_2, proof_bh_2);
1030        let path_with_bh_1 = storage
1031            .read()
1032            .await
1033            .unwrap()
1034            .get_path(Snapshot::<_, MockMerkleTree, 8>::Index(1), 0)
1035            .await
1036            .unwrap();
1037        assert_eq!(path_with_bh_1, proof_bh_1);
1038    }
1039
1040    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1041    async fn test_merklized_state_non_membership_proof() {
1042        // This test updates the Merkle tree with a new entry and inserts the corresponding Merkle nodes into the database with created = 1.
1043        // A Merkle node is then deleted from the tree.
1044        // The database is then updated to reflect the deletion of the entry with a created (block height) of 2
1045        // As the leaf node becomes a non-member, we do a universal lookup to obtain its non-membership proof path.
1046        // It is expected that the path retrieved from the tree matches the path obtained from the database.
1047
1048        let db = TmpDb::init().await;
1049        let storage = SqlStorage::connect(db.config(), StorageConnectionType::Query)
1050            .await
1051            .unwrap();
1052
1053        // define a test tree
1054        let mut test_tree = MockMerkleTree::new(MockMerkleTree::tree_height());
1055        let block_height = 1;
1056        //insert an entry into the tree
1057        test_tree.update(0, 0).unwrap();
1058        let commitment = test_tree.commitment();
1059
1060        let test_data = serde_json::json!({ MockMerkleTree::header_state_commitment_field() : serde_json::to_value(commitment).unwrap()});
1061        // insert the header with merkle commitment
1062        let mut tx = storage.write().await.unwrap();
1063        tx.upsert(
1064            "header",
1065            [
1066                "height",
1067                "hash",
1068                "payload_hash",
1069                "timestamp",
1070                "data",
1071                "ns_table",
1072            ],
1073            ["height"],
1074            [(
1075                block_height as i64,
1076                "randomString".to_string(),
1077                "t".to_string(),
1078                0,
1079                test_data,
1080                "ns_table".to_string(),
1081            )],
1082        )
1083        .await
1084        .unwrap();
1085        // proof for the index from the tree
1086        let (_, proof_before_remove) = test_tree.lookup(0).expect_ok().unwrap();
1087        // traversal path for the index.
1088        let traversal_path =
1089            <usize as ToTraversalPath<8>>::to_traversal_path(&0, test_tree.height());
1090        // insert merkle nodes
1091        UpdateStateData::<_, MockMerkleTree, 8>::insert_merkle_nodes(
1092            &mut tx,
1093            proof_before_remove.clone(),
1094            traversal_path.clone(),
1095            block_height as u64,
1096        )
1097        .await
1098        .expect("failed to insert nodes");
1099        // update saved state height
1100        UpdateStateData::<_, MockMerkleTree, 8>::set_last_state_height(&mut tx, block_height)
1101            .await
1102            .unwrap();
1103        tx.commit().await.unwrap();
1104        // the path from the db and and tree should match
1105        let merkle_path = storage
1106            .read()
1107            .await
1108            .unwrap()
1109            .get_path(
1110                Snapshot::<_, MockMerkleTree, 8>::Index(block_height as u64),
1111                0,
1112            )
1113            .await
1114            .unwrap();
1115
1116        // merkle path from the storage should match the path from test tree
1117        assert_eq!(
1118            merkle_path,
1119            proof_before_remove.clone(),
1120            "merkle paths mismatch"
1121        );
1122
1123        //Deleting the index 0
1124        test_tree.remove(0).expect("failed to delete index 0 ");
1125
1126        // Update the database with the proof
1127        // Created = 2 in this case
1128        let proof_after_remove = test_tree.universal_lookup(0).expect_not_found().unwrap();
1129
1130        let mut tx = storage.write().await.unwrap();
1131        UpdateStateData::<_, MockMerkleTree, 8>::insert_merkle_nodes(
1132            &mut tx,
1133            proof_after_remove.clone(),
1134            traversal_path.clone(),
1135            2_u64,
1136        )
1137        .await
1138        .expect("failed to insert nodes");
1139        // Insert the new header
1140        tx.upsert(
1141                "header",
1142                ["height", "hash", "payload_hash", "timestamp", "data", "ns_table"],
1143                ["height"],
1144                [(
1145                    2i64,
1146                    "randomString2".to_string(),
1147                    "t".to_string(),
1148                    0,
1149                    serde_json::json!({ MockMerkleTree::header_state_commitment_field() : serde_json::to_value(test_tree.commitment()).unwrap()}),
1150                    "ns_table".to_string(),
1151                )],
1152            )
1153            .await
1154            .unwrap();
1155        // update saved state height
1156        UpdateStateData::<_, MockMerkleTree, 8>::set_last_state_height(&mut tx, 2)
1157            .await
1158            .unwrap();
1159        tx.commit().await.unwrap();
1160        // Get non membership proof
1161        let non_membership_path = storage
1162            .read()
1163            .await
1164            .unwrap()
1165            .get_path(Snapshot::<_, MockMerkleTree, 8>::Index(2_u64), 0)
1166            .await
1167            .unwrap();
1168        // Assert that the paths from the db and the tree are equal
1169        assert_eq!(
1170            non_membership_path, proof_after_remove,
1171            "merkle paths dont match"
1172        );
1173
1174        // Query the membership proof i.e proof with created = 1
1175        // This proof should be equal to the proof before deletion
1176        // Assert that the paths from the db and the tree are equal
1177
1178        let proof_bh_1 = storage
1179            .read()
1180            .await
1181            .unwrap()
1182            .get_path(Snapshot::<_, MockMerkleTree, 8>::Index(1_u64), 0)
1183            .await
1184            .unwrap();
1185        assert_eq!(proof_bh_1, proof_before_remove, "merkle paths dont match");
1186    }
1187
1188    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1189    async fn test_merklized_state_non_membership_proof_unseen_entry() {
1190        let db = TmpDb::init().await;
1191        let storage = SqlStorage::connect(db.config(), StorageConnectionType::Query)
1192            .await
1193            .unwrap();
1194
1195        // define a test tree
1196        let mut test_tree = MockMerkleTree::new(MockMerkleTree::tree_height());
1197
1198        // For each case (where the root is empty, a leaf, and a branch) test getting a
1199        // non-membership proof for an entry node the database has never seen.
1200        for i in 0..=2 {
1201            tracing::info!(i, ?test_tree, "testing non-membership proof");
1202            let mut tx = storage.write().await.unwrap();
1203
1204            // Insert a dummy header
1205            tx.upsert(
1206                "header",
1207                ["height", "hash", "payload_hash", "timestamp", "data", "ns_table"],
1208                ["height"],
1209                [(
1210                    i as i64,
1211                    format!("hash{i}"),
1212                    "t".to_string(),
1213                    0,
1214                    serde_json::json!({ MockMerkleTree::header_state_commitment_field() : serde_json::to_value(test_tree.commitment()).unwrap()}),
1215                    "ns_table".to_string(),
1216                )],
1217            )
1218            .await
1219            .unwrap();
1220            // update saved state height
1221            UpdateStateData::<_, MockMerkleTree, 8>::set_last_state_height(&mut tx, i)
1222                .await
1223                .unwrap();
1224            tx.commit().await.unwrap();
1225
1226            // get a non-membership proof for a never-before-seen node.
1227            let proof = storage
1228                .read()
1229                .await
1230                .unwrap()
1231                .get_path(
1232                    Snapshot::<MockTypes, MockMerkleTree, 8>::Index(i as u64),
1233                    100,
1234                )
1235                .await
1236                .unwrap();
1237            assert_eq!(proof.elem(), None);
1238
1239            assert!(
1240                MockMerkleTree::non_membership_verify(test_tree.commitment(), 100, proof).unwrap()
1241            );
1242
1243            // insert an additional node into the tree.
1244            test_tree.update(i, i).unwrap();
1245            let (_, proof) = test_tree.lookup(i).expect_ok().unwrap();
1246            let traversal_path = ToTraversalPath::<8>::to_traversal_path(&i, test_tree.height());
1247            let mut tx = storage.write().await.unwrap();
1248            UpdateStateData::<_, MockMerkleTree, 8>::insert_merkle_nodes(
1249                &mut tx,
1250                proof,
1251                traversal_path,
1252                (i + 1) as u64,
1253            )
1254            .await
1255            .expect("failed to insert nodes");
1256            tx.commit().await.unwrap();
1257        }
1258    }
1259
1260    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1261    async fn test_merklized_storage_with_commit() {
1262        // This test insert a merkle path into the database and queries the path using the merkle commitment
1263
1264        let db = TmpDb::init().await;
1265        let storage = SqlStorage::connect(db.config(), StorageConnectionType::Query)
1266            .await
1267            .unwrap();
1268
1269        // define a test tree
1270        let mut test_tree = MockMerkleTree::new(MockMerkleTree::tree_height());
1271        let block_height = 1;
1272        //insert an entry into the tree
1273        test_tree.update(0, 0).unwrap();
1274        let commitment = test_tree.commitment();
1275
1276        let test_data = serde_json::json!({ MockMerkleTree::header_state_commitment_field() : serde_json::to_value(commitment).unwrap()});
1277        // insert the header with merkle commitment
1278        let mut tx = storage.write().await.unwrap();
1279        tx.upsert(
1280            "header",
1281            [
1282                "height",
1283                "hash",
1284                "payload_hash",
1285                "timestamp",
1286                "data",
1287                "ns_table",
1288            ],
1289            ["height"],
1290            [(
1291                block_height as i64,
1292                "randomString".to_string(),
1293                "t".to_string(),
1294                0,
1295                test_data,
1296                "ns_table".to_string(),
1297            )],
1298        )
1299        .await
1300        .unwrap();
1301        // proof for the index from the tree
1302        let (_, proof) = test_tree.lookup(0).expect_ok().unwrap();
1303        // traversal path for the index.
1304        let traversal_path =
1305            <usize as ToTraversalPath<8>>::to_traversal_path(&0, test_tree.height());
1306        // insert merkle nodes
1307        UpdateStateData::<_, MockMerkleTree, 8>::insert_merkle_nodes(
1308            &mut tx,
1309            proof.clone(),
1310            traversal_path.clone(),
1311            block_height as u64,
1312        )
1313        .await
1314        .expect("failed to insert nodes");
1315        // update saved state height
1316        UpdateStateData::<_, MockMerkleTree, 8>::set_last_state_height(&mut tx, block_height)
1317            .await
1318            .unwrap();
1319        tx.commit().await.unwrap();
1320
1321        let merkle_proof = storage
1322            .read()
1323            .await
1324            .unwrap()
1325            .get_path(Snapshot::<_, MockMerkleTree, 8>::Commit(commitment), 0)
1326            .await
1327            .unwrap();
1328
1329        let (_, proof) = test_tree.lookup(0).expect_ok().unwrap();
1330
1331        assert_eq!(merkle_proof, proof.clone(), "merkle paths mismatch");
1332    }
1333    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1334    async fn test_merklized_state_missing_state() {
1335        // This test checks that header commitment matches the root hash.
1336        // For this, the header merkle root commitment field is not updated, which should result in an error
1337        // The full merkle path verification is also done by recomputing the root hash
1338        // An index and its corresponding merkle nodes with created (bh) = 1 are inserted.
1339        // The entry of the index is updated, and the updated nodes are inserted with created (bh) = 2.
1340        // A node which is in the traversal path with bh = 2 is deleted, so the get_path should return an error as an older version of one of the nodes is used.
1341
1342        let db = TmpDb::init().await;
1343        let storage = SqlStorage::connect(db.config(), StorageConnectionType::Query)
1344            .await
1345            .unwrap();
1346
1347        // define a test tree
1348        let mut test_tree = MockMerkleTree::new(MockMerkleTree::tree_height());
1349        let block_height = 1;
1350        //insert an entry into the tree
1351
1352        let mut tx = storage.write().await.unwrap();
1353        for i in 0..27 {
1354            test_tree.update(i, i).unwrap();
1355            let test_data = serde_json::json!({ MockMerkleTree::header_state_commitment_field() : serde_json::to_value(test_tree.commitment()).unwrap()});
1356            // insert the header with merkle commitment
1357            tx.upsert(
1358                "header",
1359                [
1360                    "height",
1361                    "hash",
1362                    "payload_hash",
1363                    "timestamp",
1364                    "data",
1365                    "ns_table",
1366                ],
1367                ["height"],
1368                [(
1369                    block_height as i64,
1370                    format!("rarndomString{i}"),
1371                    "t".to_string(),
1372                    0,
1373                    test_data,
1374                    "ns_table".to_string(),
1375                )],
1376            )
1377            .await
1378            .unwrap();
1379            // proof for the index from the tree
1380            let (_, proof) = test_tree.lookup(i).expect_ok().unwrap();
1381            // traversal path for the index.
1382            let traversal_path =
1383                <usize as ToTraversalPath<8>>::to_traversal_path(&i, test_tree.height());
1384            // insert merkle nodes
1385            UpdateStateData::<_, MockMerkleTree, 8>::insert_merkle_nodes(
1386                &mut tx,
1387                proof.clone(),
1388                traversal_path.clone(),
1389                block_height as u64,
1390            )
1391            .await
1392            .expect("failed to insert nodes");
1393            // update saved state height
1394            UpdateStateData::<_, MockMerkleTree, 8>::set_last_state_height(&mut tx, block_height)
1395                .await
1396                .unwrap();
1397        }
1398
1399        test_tree.update(1, 100).unwrap();
1400        //insert updated merkle path without updating the header
1401        let traversal_path =
1402            <usize as ToTraversalPath<8>>::to_traversal_path(&1, test_tree.height());
1403        let (_, proof) = test_tree.lookup(1).expect_ok().unwrap();
1404
1405        // insert merkle nodes
1406        UpdateStateData::<_, MockMerkleTree, 8>::insert_merkle_nodes(
1407            &mut tx,
1408            proof.clone(),
1409            traversal_path.clone(),
1410            block_height as u64,
1411        )
1412        .await
1413        .expect("failed to insert nodes");
1414        tx.commit().await.unwrap();
1415
1416        let merkle_path = storage
1417            .read()
1418            .await
1419            .unwrap()
1420            .get_path(
1421                Snapshot::<_, MockMerkleTree, 8>::Index(block_height as u64),
1422                1,
1423            )
1424            .await;
1425        assert!(merkle_path.is_err());
1426
1427        let test_data = serde_json::json!({ MockMerkleTree::header_state_commitment_field() : serde_json::to_value(test_tree.commitment()).unwrap()});
1428        // insert the header with merkle commitment
1429        let mut tx = storage.write().await.unwrap();
1430        tx.upsert(
1431            "header",
1432            [
1433                "height",
1434                "hash",
1435                "payload_hash",
1436                "timestamp",
1437                "data",
1438                "ns_table",
1439            ],
1440            ["height"],
1441            [(
1442                block_height as i64,
1443                "randomStringgg".to_string(),
1444                "t".to_string(),
1445                0,
1446                test_data,
1447                "ns_table".to_string(),
1448            )],
1449        )
1450        .await
1451        .unwrap();
1452        tx.commit().await.unwrap();
1453        // Querying the path again
1454        let merkle_proof = storage
1455            .read()
1456            .await
1457            .unwrap()
1458            .get_path(
1459                Snapshot::<_, MockMerkleTree, 8>::Index(block_height as u64),
1460                1,
1461            )
1462            .await
1463            .unwrap();
1464        assert_eq!(merkle_proof, proof, "path dont match");
1465
1466        // Update the tree again for index 0 with created (bh) = 2
1467        // Delete one of the node in the traversal path
1468        test_tree.update(1, 200).unwrap();
1469
1470        let (_, proof) = test_tree.lookup(1).expect_ok().unwrap();
1471        let test_data = serde_json::json!({ MockMerkleTree::header_state_commitment_field() : serde_json::to_value(test_tree.commitment()).unwrap()});
1472
1473        // insert the header with merkle commitment
1474        let mut tx = storage.write().await.unwrap();
1475        tx.upsert(
1476            "header",
1477            [
1478                "height",
1479                "hash",
1480                "payload_hash",
1481                "timestamp",
1482                "data",
1483                "ns_table",
1484            ],
1485            ["height"],
1486            [(
1487                2i64,
1488                "randomHashString".to_string(),
1489                "t".to_string(),
1490                0,
1491                test_data,
1492                "ns_table".to_string(),
1493            )],
1494        )
1495        .await
1496        .unwrap();
1497        UpdateStateData::<_, MockMerkleTree, 8>::insert_merkle_nodes(
1498            &mut tx,
1499            proof.clone(),
1500            traversal_path.clone(),
1501            2_u64,
1502        )
1503        .await
1504        .expect("failed to insert nodes");
1505
1506        // Deleting one internal node
1507        let node_path = traversal_path
1508            .iter()
1509            .skip(1)
1510            .rev()
1511            .map(|n| *n as i32)
1512            .collect::<Vec<_>>();
1513        tx.execute(
1514            query(&format!(
1515                "DELETE FROM {} WHERE created = 2 and path = $1",
1516                MockMerkleTree::state_type()
1517            ))
1518            .bind(serde_json::to_value(node_path).unwrap()),
1519        )
1520        .await
1521        .expect("failed to delete internal node");
1522        tx.commit().await.unwrap();
1523
1524        let merkle_path = storage
1525            .read()
1526            .await
1527            .unwrap()
1528            .get_path(Snapshot::<_, MockMerkleTree, 8>::Index(2_u64), 1)
1529            .await;
1530
1531        assert!(merkle_path.is_err());
1532    }
1533
1534    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1535    async fn test_merklized_state_snapshot() {
1536        let db = TmpDb::init().await;
1537        let storage = SqlStorage::connect(db.config(), StorageConnectionType::Query)
1538            .await
1539            .unwrap();
1540
1541        // Define a test tree
1542        let mut test_tree = MockMerkleTree::new(MockMerkleTree::tree_height());
1543
1544        // We will sample random keys as u32. This is a value that is not a valid u32 and thus is a
1545        // key we will never insert into the tree.
1546        const RESERVED_KEY: usize = (u32::MAX as usize) + 1;
1547
1548        // Randomly insert and delete some entries. For each entry we insert, we also keep track of
1549        // whether the entry should be in the tree using a HashMap.
1550        #[tracing::instrument(skip(tree, expected))]
1551        fn randomize(tree: &mut MockMerkleTree, expected: &mut HashMap<usize, Option<usize>>) {
1552            let mut rng = rand::thread_rng();
1553            tracing::info!("randomizing tree");
1554
1555            for _ in 0..50 {
1556                // We flip a coin to decide whether to insert or delete, unless the tree is empty,
1557                // in which case we can only insert.
1558                if !expected.values().any(|v| v.is_some()) || rng.next_u32().is_multiple_of(2) {
1559                    // Insert.
1560                    let key = rng.next_u32() as usize;
1561                    let val = rng.next_u32() as usize;
1562                    tracing::info!(key, val, "inserting");
1563
1564                    tree.update(key, val).unwrap();
1565                    expected.insert(key, Some(val));
1566                } else {
1567                    // Delete.
1568                    let key = expected
1569                        .iter()
1570                        .filter_map(|(k, v)| if v.is_some() { Some(k) } else { None })
1571                        .choose(&mut rng)
1572                        .unwrap();
1573                    tracing::info!(key, "deleting");
1574
1575                    tree.remove(key).unwrap();
1576                    expected.insert(*key, None);
1577                }
1578            }
1579        }
1580
1581        // Commit the tree to storage.
1582        #[tracing::instrument(skip(storage, tree, expected))]
1583        async fn store(
1584            storage: &SqlStorage,
1585            tree: &MockMerkleTree,
1586            expected: &HashMap<usize, Option<usize>>,
1587            block_height: u64,
1588        ) {
1589            tracing::info!("persisting tree");
1590            let mut tx = storage.write().await.unwrap();
1591
1592            for key in expected.keys() {
1593                let proof = match tree.universal_lookup(key) {
1594                    LookupResult::Ok(_, proof) => proof,
1595                    LookupResult::NotFound(proof) => proof,
1596                    LookupResult::NotInMemory => panic!("failed to find key {key}"),
1597                };
1598                let traversal_path = ToTraversalPath::<8>::to_traversal_path(key, tree.height());
1599                UpdateStateData::<_, MockMerkleTree, 8>::insert_merkle_nodes(
1600                    &mut tx,
1601                    proof,
1602                    traversal_path,
1603                    block_height,
1604                )
1605                .await
1606                .unwrap();
1607            }
1608            // insert the header with merkle commitment
1609            tx
1610            .upsert("header", ["height", "hash", "payload_hash", "timestamp", "data", "ns_table"], ["height"],
1611                [(
1612                    block_height as i64,
1613                    format!("hash{block_height}"),
1614                    "hash".to_string(),
1615                    0i64,
1616                    serde_json::json!({ MockMerkleTree::header_state_commitment_field() : serde_json::to_value(tree.commitment()).unwrap()}),
1617                    "ns_table".to_string(),
1618                )],
1619            )
1620            .await
1621            .unwrap();
1622            UpdateStateData::<MockTypes, MockMerkleTree, 8>::set_last_state_height(
1623                &mut tx,
1624                block_height as usize,
1625            )
1626            .await
1627            .unwrap();
1628            tx.commit().await.unwrap();
1629        }
1630
1631        #[tracing::instrument(skip(storage, tree, expected))]
1632        async fn validate(
1633            storage: &SqlStorage,
1634            tree: &MockMerkleTree,
1635            expected: &HashMap<usize, Option<usize>>,
1636            block_height: u64,
1637        ) {
1638            tracing::info!("validating snapshot");
1639
1640            // Check that we can get a correct path for each key that we touched.
1641            let snapshot = Snapshot::<_, MockMerkleTree, 8>::Index(block_height);
1642
1643            for (key, val) in expected {
1644                let proof = match tree.universal_lookup(key) {
1645                    LookupResult::Ok(_, proof) => proof,
1646                    LookupResult::NotFound(proof) => proof,
1647                    LookupResult::NotInMemory => panic!("failed to find key {key}"),
1648                };
1649                assert_eq!(
1650                    proof,
1651                    storage
1652                        .read()
1653                        .await
1654                        .unwrap()
1655                        .get_path(snapshot, *key)
1656                        .await
1657                        .unwrap()
1658                );
1659                assert_eq!(val.as_ref(), proof.elem());
1660                // Check path is valid for test_tree
1661                if val.is_some() {
1662                    MockMerkleTree::verify(tree.commitment(), key, proof)
1663                        .unwrap()
1664                        .unwrap();
1665                } else {
1666                    assert!(
1667                        MockMerkleTree::non_membership_verify(tree.commitment(), key, proof)
1668                            .unwrap()
1669                    );
1670                }
1671            }
1672
1673            // Check that we can even get a non-membership proof for a key that we never touched.
1674            let proof = match tree.universal_lookup(RESERVED_KEY) {
1675                LookupResult::Ok(_, proof) => proof,
1676                LookupResult::NotFound(proof) => proof,
1677                LookupResult::NotInMemory => panic!("failed to find reserved key {RESERVED_KEY}"),
1678            };
1679            assert_eq!(
1680                proof,
1681                storage
1682                    .read()
1683                    .await
1684                    .unwrap()
1685                    .get_path(snapshot, RESERVED_KEY)
1686                    .await
1687                    .unwrap()
1688            );
1689            assert_eq!(proof.elem(), None);
1690            // Check path is valid for test_tree
1691            assert!(
1692                MockMerkleTree::non_membership_verify(tree.commitment(), RESERVED_KEY, proof)
1693                    .unwrap()
1694            );
1695        }
1696
1697        // Create a randomized Merkle tree.
1698        let mut expected = HashMap::<usize, Option<usize>>::new();
1699        randomize(&mut test_tree, &mut expected);
1700
1701        // Commit the randomized tree to storage.
1702        store(&storage, &test_tree, &expected, 1).await;
1703        validate(&storage, &test_tree, &expected, 1).await;
1704
1705        // Make random edits and commit another snapshot.
1706        let mut expected2 = expected.clone();
1707        let mut test_tree2 = test_tree.clone();
1708        randomize(&mut test_tree2, &mut expected2);
1709        store(&storage, &test_tree2, &expected2, 2).await;
1710        validate(&storage, &test_tree2, &expected2, 2).await;
1711
1712        // Ensure the original snapshot is still valid.
1713        validate(&storage, &test_tree, &expected, 1).await;
1714    }
1715
1716    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1717    async fn test_merklized_state_missing_leaf() {
1718        // Check that if a leaf is missing but its ancestors are present/key is in the tree, we
1719        // catch it rather than interpreting the entry as an empty node by default. Note that this
1720        // scenario should be impossible in normal usage, since we never store or delete partial
1721        // paths. But we should never return an invalid proof even in extreme cases like database
1722        // corruption.
1723
1724        for tree_size in 1..=3 {
1725            let db = TmpDb::init().await;
1726            let storage = SqlStorage::connect(db.config(), StorageConnectionType::Query)
1727                .await
1728                .unwrap();
1729
1730            // Define a test tree
1731            let mut test_tree = MockMerkleTree::new(MockMerkleTree::tree_height());
1732            for i in 0..tree_size {
1733                test_tree.update(i, i).unwrap();
1734            }
1735
1736            let mut tx = storage.write().await.unwrap();
1737
1738            // Insert a header with the tree commitment.
1739            tx.upsert(
1740                "header",
1741                ["height", "hash", "payload_hash", "timestamp", "data", "ns_table"],
1742                ["height"],
1743                [(
1744                    0i64,
1745                    "hash".to_string(),
1746                    "hash".to_string(),
1747                    0,
1748                    serde_json::json!({ MockMerkleTree::header_state_commitment_field() : serde_json::to_value(test_tree.commitment()).unwrap()}),
1749                    "ns_table".to_string(),
1750                )],
1751            )
1752            .await
1753            .unwrap();
1754
1755            // Insert Merkle nodes.
1756            for i in 0..tree_size {
1757                let proof = test_tree.lookup(i).expect_ok().unwrap().1;
1758                let traversal_path =
1759                    ToTraversalPath::<8>::to_traversal_path(&i, test_tree.height());
1760                UpdateStateData::<_, MockMerkleTree, 8>::insert_merkle_nodes(
1761                    &mut tx,
1762                    proof,
1763                    traversal_path,
1764                    0,
1765                )
1766                .await
1767                .unwrap();
1768            }
1769            UpdateStateData::<_, MockMerkleTree, 8>::set_last_state_height(&mut tx, 0)
1770                .await
1771                .unwrap();
1772            tx.commit().await.unwrap();
1773
1774            // Test that we can get all the entries.
1775            let snapshot = Snapshot::<MockTypes, MockMerkleTree, 8>::Index(0);
1776            for i in 0..tree_size {
1777                let proof = test_tree.lookup(i).expect_ok().unwrap().1;
1778                assert_eq!(
1779                    proof,
1780                    storage
1781                        .read()
1782                        .await
1783                        .unwrap()
1784                        .get_path(snapshot, i)
1785                        .await
1786                        .unwrap()
1787                );
1788                assert_eq!(*proof.elem().unwrap(), i);
1789            }
1790
1791            // Now delete the leaf node for the last entry we inserted, corrupting the database.
1792            let index = serde_json::to_value(tree_size - 1).unwrap();
1793            let mut tx = storage.write().await.unwrap();
1794
1795            tx.execute(
1796                query(&format!(
1797                    "DELETE FROM {} WHERE idx = $1",
1798                    MockMerkleTree::state_type()
1799                ))
1800                .bind(serde_json::to_value(index).unwrap()),
1801            )
1802            .await
1803            .unwrap();
1804            tx.commit().await.unwrap();
1805
1806            // Test that we can still get the entries we didn't delete.
1807            for i in 0..tree_size - 1 {
1808                let proof = test_tree.lookup(i).expect_ok().unwrap().1;
1809                assert_eq!(
1810                    proof,
1811                    storage
1812                        .read()
1813                        .await
1814                        .unwrap()
1815                        .get_path(snapshot, i)
1816                        .await
1817                        .unwrap()
1818                );
1819                assert_eq!(*proof.elem().unwrap(), i);
1820            }
1821
1822            // Looking up the entry we deleted fails, rather than return an invalid path.
1823            let err = storage
1824                .read()
1825                .await
1826                .unwrap()
1827                .get_path(snapshot, tree_size - 1)
1828                .await
1829                .unwrap_err();
1830            assert!(matches!(err, QueryError::Missing));
1831        }
1832    }
1833}