Skip to main content

confium_log_server/
merkle.rs

1//! Merkle tree state for the log server.
2//!
3//! Wraps `confium_transparency::MerkleTree` with persistence helpers
4//! that rebuild the tree from the database on startup. After the
5//! initial rebuild, the in-memory tree is the source of truth for
6//! inclusion / consistency proofs; the database is the source of
7//! truth for leaf entries.
8
9use anyhow::{Context, Result};
10use confium_transparency::{
11    entry::{ArtifactType, MerkleEntry},
12    merkle::{Hash, InclusionProof, MerkleError, MerkleTree},
13};
14
15use crate::db::Database;
16
17pub struct MerkleState {
18    pub tree: MerkleTree,
19}
20
21impl MerkleState {
22    /// Rebuild the Merkle tree from every entry in the database.
23    /// O(N) on startup; subsequent appends are O(log N).
24    ///
25    /// Leaf hashes cover the entry's sequence, timestamp, and
26    /// artifact hash, so the rebuild reuses the *stored* timestamps
27    /// — freshly stamped ones would silently change every leaf and
28    /// invalidate every proof issued before the restart.
29    pub fn from_db(db: &Database) -> Result<Self> {
30        let rows = db
31            .all_entries_for_rebuild()
32            .context("loading entries for rebuild")?;
33        let mut tree = MerkleTree::new();
34        for row in rows {
35            let entry = MerkleEntry {
36                sequence: row.sequence,
37                timestamp: row.timestamp,
38                artifact_type: row.artifact_type,
39                artifact_hash: row.artifact_hash,
40                metadata: serde_json::Value::Null,
41            };
42            tree.append(entry);
43        }
44        tracing::info!(count = tree.len(), "rebuilt Merkle tree");
45        Ok(MerkleState { tree })
46    }
47
48    /// Append one leaf. The timestamp must be the same one stored in
49    /// the database for this entry — the two must never diverge or
50    /// a later rebuild produces a different tree.
51    pub fn append(
52        &mut self,
53        leaf: Hash,
54        artifact_type: ArtifactType,
55        timestamp: chrono::DateTime<chrono::Utc>,
56    ) -> u64 {
57        let entry = MerkleEntry {
58            sequence: 0,
59            timestamp,
60            artifact_type,
61            artifact_hash: leaf,
62            metadata: serde_json::Value::Null,
63        };
64        self.tree.append(entry)
65    }
66
67    pub fn root(&self) -> Hash {
68        self.tree.root()
69    }
70
71    pub fn len(&self) -> u64 {
72        self.tree.len() as u64
73    }
74
75    pub fn inclusion_proof(
76        &self,
77        sequence: u64,
78    ) -> std::result::Result<InclusionProof, MerkleError> {
79        self.tree.inclusion_proof(sequence)
80    }
81
82    pub fn consistency_proof(&self, old_size: u64) -> std::result::Result<Vec<Hash>, MerkleError> {
83        self.tree.consistency_proof(old_size as usize)
84    }
85}