Skip to main content

tycho_rpc/endpoint/jrpc/
cache.rs

1use std::sync::{Arc, OnceLock};
2
3use arc_swap::ArcSwapOption;
4use base64::prelude::{BASE64_STANDARD, Engine as _};
5use moka::sync::Cache;
6use serde::Serialize;
7use serde_json::value::RawValue;
8use tycho_types::boc::{Boc, BocRepr};
9use tycho_types::cell::HashBytes;
10use tycho_types::models::{Block, BlockId, BlockchainConfig};
11use tycho_types::prelude::Cell;
12use tycho_util::{FastHasherState, serde_helpers};
13
14pub struct JrpcEndpointCache {
15    libraries: Cache<HashBytes, CachedJson, FastHasherState>,
16    key_block_proofs: Cache<u32, CachedJson, FastHasherState>,
17    latest_key_block: ArcSwapOption<Box<RawValue>>,
18    blockchain_config: ArcSwapOption<Box<RawValue>>,
19}
20
21impl Default for JrpcEndpointCache {
22    fn default() -> Self {
23        Self {
24            libraries: Cache::builder()
25                .max_capacity(100)
26                .build_with_hasher(Default::default()),
27            key_block_proofs: Cache::builder()
28                .max_capacity(10)
29                .build_with_hasher(Default::default()),
30            latest_key_block: Default::default(),
31            blockchain_config: Default::default(),
32        }
33    }
34}
35
36impl JrpcEndpointCache {
37    pub fn insert_library_cell_response(
38        &self,
39        hash_bytes: HashBytes,
40        cell: Option<Cell>,
41    ) -> CachedJson {
42        static EMPTY: OnceLock<CachedJson> = OnceLock::new();
43
44        match cell {
45            None => EMPTY
46                .get_or_init(|| make_cached(GetLibraryCellResponse { cell: None }))
47                .clone(),
48            Some(cell) => {
49                let res = make_cached(GetLibraryCellResponse {
50                    cell: Some(Boc::encode_base64(cell)),
51                });
52                self.libraries.insert(hash_bytes, res.clone());
53                res
54            }
55        }
56    }
57
58    pub fn get_library_cell_response(&self, hash: &HashBytes) -> Option<CachedJson> {
59        self.libraries.get(hash)
60    }
61
62    pub fn insert_key_block_proof_response(
63        &self,
64        seqno: u32,
65        proof: Option<(BlockId, impl AsRef<[u8]>)>,
66    ) -> CachedJson {
67        static EMPTY: OnceLock<CachedJson> = OnceLock::new();
68
69        match proof {
70            None => EMPTY
71                .get_or_init(|| {
72                    make_cached(BlockProofResponse {
73                        block_id: None,
74                        proof: None,
75                    })
76                })
77                .clone(),
78            Some((block_id, proof)) => {
79                let res = make_cached(BlockProofResponse {
80                    block_id: Some(block_id),
81                    proof: Some(BASE64_STANDARD.encode(proof)),
82                });
83                self.key_block_proofs.insert(seqno, res.clone());
84                res
85            }
86        }
87    }
88
89    pub fn get_key_block_proof_response(&self, seqno: u32) -> Option<CachedJson> {
90        self.key_block_proofs.get(&seqno)
91    }
92
93    pub fn load_latest_key_block(&self) -> arc_swap::Guard<Option<CachedJson>> {
94        self.latest_key_block.load()
95    }
96
97    pub fn load_blockchain_config(&self) -> arc_swap::Guard<Option<CachedJson>> {
98        self.blockchain_config.load()
99    }
100
101    pub fn handle_config(&self, global_id: i32, seqno: u32, config: &BlockchainConfig) {
102        self.blockchain_config.store(
103            match serde_json::value::to_raw_value(&LatestBlockchainConfigRef {
104                global_id,
105                seqno,
106                config,
107            }) {
108                Ok(value) => Some(Arc::new(value)),
109                Err(e) => {
110                    tracing::error!("failed to serialize blockchain config json: {e:?}");
111                    None
112                }
113            },
114        );
115    }
116
117    pub fn handle_key_block(&self, block: &Block) {
118        self.latest_key_block.store(
119            match serde_json::value::to_raw_value(&LatestKeyBlockRef { block }) {
120                Ok(value) => Some(Arc::new(value)),
121                Err(e) => {
122                    tracing::error!("failed to serialize key block json: {e:?}");
123                    None
124                }
125            },
126        );
127    }
128}
129
130#[derive(Serialize)]
131struct GetLibraryCellResponse {
132    cell: Option<String>,
133}
134
135// TODO: Add last_known_mc_seqno.
136#[derive(Serialize)]
137#[serde(rename_all = "camelCase")]
138struct BlockProofResponse {
139    #[serde(
140        with = "serde_helpers::option_string",
141        skip_serializing_if = "Option::is_none"
142    )]
143    block_id: Option<BlockId>,
144    proof: Option<String>,
145}
146
147#[derive(Debug, Clone, Serialize)]
148struct LatestKeyBlockRef<'a> {
149    #[serde(with = "BocRepr")]
150    block: &'a Block,
151}
152
153#[derive(Debug, Clone, Serialize)]
154#[serde(rename_all = "camelCase")]
155struct LatestBlockchainConfigRef<'a> {
156    global_id: i32,
157    seqno: u32,
158    #[serde(with = "BocRepr")]
159    config: &'a BlockchainConfig,
160}
161
162type CachedJson = Arc<Box<RawValue>>;
163
164fn make_cached<T: Serialize>(value: T) -> CachedJson {
165    Arc::new(serde_json::value::to_raw_value(&value).unwrap())
166}