tycho_rpc/endpoint/jrpc/
cache.rs1use 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#[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}