Skip to main content

hashiverse_server_lib/environment/
environment_store.rs

1//! # [`EnvironmentStore`] trait — pluggable server-side persistence
2//!
3//! The contract every backend must implement: CRUD on post bundles, per-bundle
4//! metadata, config, and aggregated feedback. Keeps the
5//! [`crate::environment::environment::Environment`] facade independent of whether
6//! the bytes actually live on disk ([`crate::environment::disk_environment_store`])
7//! or in RAM for tests ([`crate::environment::mem_environment_store`]).
8//!
9//! Design notes:
10//!
11//! - **Last-accessed tracking** on every read so decimation can evict coldest-first
12//!   without a second pass.
13//! - **Composite feedback keys** `(location_id, post_id, feedback_type)` — the store
14//!   only persists the write when the new entry's PoW is at least as strong as the
15//!   existing one, so backend can resolve merges without returning any state to the
16//!   caller.
17//! - **Config helpers** (`get_config_usize` / `set_config_usize`) for the small set
18//!   of server tunables that outlive any single restart.
19
20use bytes::Bytes;
21use crate::environment::environment::PostBundleMetadata;
22use anyhow::Context;
23use hashiverse_lib::tools::time::{TimeMillis, TimeMillisBytes};
24use hashiverse_lib::tools::types::Id;
25use std::collections::HashMap;
26use hashiverse_lib::protocol::posting::encoded_post_feedback::EncodedPostFeedbackV1;
27
28pub trait EnvironmentStore: Sync + Send {
29    fn post_bundle_count(&self) -> anyhow::Result<usize>;
30    fn post_bundle_metadata_get(&self, location_id: &Id) -> anyhow::Result<Option<PostBundleMetadata>>;
31    fn post_bundle_metadata_put(&self, location_id: &Id, post_bundle_metadata: &PostBundleMetadata) -> anyhow::Result<()>;
32    fn post_bundle_bytes_get(&self, location_id: &Id) -> anyhow::Result<Option<Bytes>>;
33    fn post_bundle_bytes_put(&self, location_id: &Id, bytes: &[u8]) -> anyhow::Result<()>;
34    fn post_bundles_last_accessed_flush(&self, post_bundles_last_accessed: &HashMap<Id, TimeMillis>) -> anyhow::Result<()>;
35    fn post_bundles_delete(&self, location_ids: &[Id]) -> anyhow::Result<()>;
36    fn post_bundles_last_accessed_iter(&self, location_id: &Id) -> Box<dyn Iterator<Item = Result<(Id, TimeMillisBytes), anyhow::Error>> + '_>;
37    fn config_get_bytes(&self, key: &str) -> anyhow::Result<Option<Vec<u8>>>;
38    fn config_put_bytes(&self, key: &str, v: Vec<u8>) -> anyhow::Result<()>;
39    fn config_get_usize(&self, key: &str) -> anyhow::Result<Option<usize>> {
40        let Some(bytes_guard) = self.config_get_bytes(key)?
41        else {
42            return Ok(None);
43        };
44
45        let bytes: &[u8] = bytes_guard.as_ref();
46        let arr: [u8; size_of::<usize>()] = bytes.try_into().with_context(|| format!("stored data has wrong byte length: {}", bytes.len()))?;
47
48        Ok(Some(usize::from_be_bytes(arr)))
49    }
50    fn config_put_usize(&self, key: &str, v: usize) -> anyhow::Result<()> {
51        self.config_put_bytes(key, v.to_be_bytes().to_vec())?;
52        Ok(())
53    }
54
55    /// Get all the feedbacks for a given post_bundle_location_id
56    ///
57    /// Returns a concatenated array of EncodedPostFeedbackV1 - may be []
58    fn post_bundle_feedbacks_bytes_get(&self, post_bundle_location_id: &Id) -> anyhow::Result<Bytes>;
59
60    /// Stores the post feedback if it is more powerful than the current feedback
61    fn post_feedback_put_if_more_powerful(&self, location_id: &Id, encoded_post_feedback: &EncodedPostFeedbackV1) -> anyhow::Result<()>;
62}