1use std::path::Path;
2use std::sync::{Arc, Mutex};
3
4use jmt::storage::{TreeReader, TreeWriter};
5use jmt::{KeyHash, Version};
6use sov_schema_db::DB;
7
8use crate::rocks_db_config::gen_rocksdb_options;
9use crate::schema::tables::{JmtNodes, JmtValues, KeyHashToKey, STATE_TABLES};
10use crate::schema::types::StateKey;
11
12#[derive(Clone, Debug)]
18pub struct StateDB {
19 db: Arc<DB>,
21 next_version: Arc<Mutex<Version>>,
23}
24
25const STATE_DB_PATH_SUFFIX: &str = "state";
26
27impl StateDB {
28 pub fn with_path(path: impl AsRef<Path>) -> Result<Self, anyhow::Error> {
31 let path = path.as_ref().join(STATE_DB_PATH_SUFFIX);
32 let inner = DB::open(
33 path,
34 "state-db",
35 STATE_TABLES.iter().copied(),
36 &gen_rocksdb_options(&Default::default(), false),
37 )?;
38
39 let next_version = Self::last_version_written(&inner)?.unwrap_or_default() + 1;
40
41 Ok(Self {
42 db: Arc::new(inner),
43 next_version: Arc::new(Mutex::new(next_version)),
44 })
45 }
46
47 pub fn put_preimage(&self, key_hash: KeyHash, key: &Vec<u8>) -> Result<(), anyhow::Error> {
50 self.db.put::<KeyHashToKey>(&key_hash.0, key)
51 }
52
53 pub fn get_value_option_by_key(
55 &self,
56 version: Version,
57 key: &StateKey,
58 ) -> anyhow::Result<Option<jmt::OwnedValue>> {
59 let mut iter = self.db.iter::<JmtValues>()?;
60 iter.seek_for_prev(&(&key, version))?;
62 let found = iter.next();
63 match found {
64 Some(result) => {
65 let ((found_key, found_version), value) = result?;
66 if &found_key == key {
67 anyhow::ensure!(found_version <= version, "Bug! iterator isn't returning expected values. expected a version <= {version:} but found {found_version:}");
68 Ok(value)
69 } else {
70 Ok(None)
71 }
72 }
73 None => Ok(None),
74 }
75 }
76
77 pub fn inc_next_version(&self) {
79 let mut version = self.next_version.lock().unwrap();
80 *version += 1;
81 }
82
83 pub fn get_next_version(&self) -> Version {
85 let version = self.next_version.lock().unwrap();
86 *version
87 }
88
89 fn last_version_written(db: &DB) -> anyhow::Result<Option<Version>> {
90 let mut iter = db.iter::<JmtValues>()?;
91 iter.seek_to_last();
92
93 let version = match iter.next() {
94 Some(Ok(((_, version), _))) => Some(version),
95 _ => None,
96 };
97 Ok(version)
98 }
99}
100
101impl TreeReader for StateDB {
102 fn get_node_option(
103 &self,
104 node_key: &jmt::storage::NodeKey,
105 ) -> anyhow::Result<Option<jmt::storage::Node>> {
106 self.db.get::<JmtNodes>(node_key)
107 }
108
109 fn get_value_option(
110 &self,
111 version: Version,
112 key_hash: KeyHash,
113 ) -> anyhow::Result<Option<jmt::OwnedValue>> {
114 if let Some(key) = self.db.get::<KeyHashToKey>(&key_hash.0)? {
115 self.get_value_option_by_key(version, &key)
116 } else {
117 Ok(None)
118 }
119 }
120
121 fn get_rightmost_leaf(
122 &self,
123 ) -> anyhow::Result<Option<(jmt::storage::NodeKey, jmt::storage::LeafNode)>> {
124 todo!("StateDB does not support [`TreeReader::get_rightmost_leaf`] yet")
125 }
126}
127
128impl TreeWriter for StateDB {
129 fn write_node_batch(&self, node_batch: &jmt::storage::NodeBatch) -> anyhow::Result<()> {
130 for (node_key, node) in node_batch.nodes() {
131 self.db.put::<JmtNodes>(node_key, node)?;
132 }
133
134 for ((version, key_hash), value) in node_batch.values() {
135 let key_preimage =
136 self.db
137 .get::<KeyHashToKey>(&key_hash.0)?
138 .ok_or(anyhow::format_err!(
139 "Could not find preimage for key hash {key_hash:?}. Has `StateDB::put_preimage` been called for this key?"
140 ))?;
141 self.db.put::<JmtValues>(&(key_preimage, *version), value)?;
142 }
143 Ok(())
144 }
145}
146
147#[cfg(feature = "arbitrary")]
148pub mod arbitrary {
149 use core::ops::{Deref, DerefMut};
152
153 use proptest::strategy::LazyJust;
154 use tempfile::TempDir;
155
156 use super::*;
157
158 #[derive(Debug)]
163 pub struct ArbitraryDB {
164 pub db: StateDB,
166 pub path: TempDir,
168 }
169
170 #[derive(Debug)]
175 pub struct FallibleArbitraryStateDB {
176 pub result: anyhow::Result<ArbitraryDB>,
178 }
179
180 impl Deref for ArbitraryDB {
181 type Target = StateDB;
182
183 fn deref(&self) -> &Self::Target {
184 &self.db
185 }
186 }
187
188 impl DerefMut for ArbitraryDB {
189 fn deref_mut(&mut self) -> &mut Self::Target {
190 &mut self.db
191 }
192 }
193
194 impl<'a> ::arbitrary::Arbitrary<'a> for ArbitraryDB {
195 fn arbitrary(_u: &mut ::arbitrary::Unstructured<'a>) -> ::arbitrary::Result<Self> {
196 let path = TempDir::new().map_err(|_| ::arbitrary::Error::NotEnoughData)?;
197 let db = StateDB::with_path(&path).map_err(|_| ::arbitrary::Error::IncorrectFormat)?;
198 Ok(Self { db, path })
199 }
200 }
201
202 impl proptest::arbitrary::Arbitrary for FallibleArbitraryStateDB {
203 type Parameters = ();
204 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
205 fn gen() -> FallibleArbitraryStateDB {
206 FallibleArbitraryStateDB {
207 result: TempDir::new()
208 .map_err(|e| {
209 anyhow::anyhow!(format!("failed to generate path for StateDB: {e}"))
210 })
211 .and_then(|path| {
212 let db = StateDB::with_path(&path)?;
213 Ok(ArbitraryDB { db, path })
214 }),
215 }
216 }
217 LazyJust::new(gen)
218 }
219
220 type Strategy = LazyJust<Self, fn() -> FallibleArbitraryStateDB>;
221 }
222}
223
224#[cfg(test)]
225mod state_db_tests {
226 use jmt::storage::{NodeBatch, TreeReader, TreeWriter};
227 use jmt::KeyHash;
228
229 use super::StateDB;
230
231 #[test]
232 fn test_simple() {
233 let tmpdir = tempfile::tempdir().unwrap();
234 let db = StateDB::with_path(tmpdir.path()).unwrap();
235 let key_hash = KeyHash([1u8; 32]);
236 let key = vec![2u8; 100];
237 let value = [8u8; 150];
238
239 db.put_preimage(key_hash, &key).unwrap();
240 let mut batch = NodeBatch::default();
241 batch.extend(vec![], vec![((0, key_hash), Some(value.to_vec()))]);
242 db.write_node_batch(&batch).unwrap();
243
244 let found = db.get_value(0, key_hash).unwrap();
245 assert_eq!(found, value);
246
247 let found = db.get_value_option_by_key(0, &key).unwrap().unwrap();
248 assert_eq!(found, value);
249 }
250}