1use crate::{
2 VersionedCompressedBlock,
3 config::Config,
4 eviction_policy::CacheEvictor,
5 ports::{
6 EvictorDb,
7 TemporalRegistry,
8 UtxoIdToPointer,
9 },
10 registry::{
11 EvictorDbAll,
12 PerRegistryKeyspace,
13 RegistrationsPerTable,
14 TemporalRegistryAll,
15 },
16};
17use anyhow::Context;
18use fuel_core_types::{
19 blockchain::block::Block,
20 fuel_compression::{
21 CompressibleBy,
22 ContextError,
23 RegistryKey,
24 },
25 fuel_tx::{
26 CompressedUtxoId,
27 ScriptCode,
28 TxPointer,
29 UtxoId,
30 input::PredicateCode,
31 },
32 fuel_types::{
33 Address,
34 AssetId,
35 ContractId,
36 },
37 tai64::Tai64,
38};
39use std::collections::{
40 HashMap,
41 HashSet,
42};
43
44#[cfg(not(feature = "fault-proving"))]
45pub mod not_fault_proving {
46 use super::*;
47 pub trait CompressDb: TemporalRegistryAll + EvictorDbAll + UtxoIdToPointer {}
48 impl<T> CompressDb for T where T: TemporalRegistryAll + EvictorDbAll + UtxoIdToPointer {}
49}
50
51#[cfg(feature = "fault-proving")]
52pub mod fault_proving {
53 use super::*;
54 use crate::ports::GetRegistryRoot;
55 pub trait CompressDb:
56 TemporalRegistryAll + EvictorDbAll + UtxoIdToPointer + GetRegistryRoot
57 {
58 }
59 impl<T> CompressDb for T where
60 T: TemporalRegistryAll + EvictorDbAll + UtxoIdToPointer + GetRegistryRoot
61 {
62 }
63}
64
65#[cfg(feature = "fault-proving")]
66use fault_proving::CompressDb;
67
68#[cfg(not(feature = "fault-proving"))]
69use not_fault_proving::CompressDb;
70
71pub async fn compress<D>(
75 config: &'_ Config,
76 mut db: D,
77 block: &Block,
78) -> anyhow::Result<VersionedCompressedBlock>
79where
80 D: CompressDb,
81{
82 let target = block.transactions_vec();
83
84 let mut prepare_ctx = PrepareCtx {
85 config,
86 timestamp: block.header().time(),
87 db: &mut db,
88 accessed_keys: Default::default(),
89 };
90 let _ = target.compress_with(&mut prepare_ctx).await?;
91
92 let mut ctx = prepare_ctx.into_compression_context()?;
93 let transactions = target.compress_with(&mut ctx).await?;
94 let registrations: RegistrationsPerTable = ctx.finalize()?;
95
96 #[cfg(feature = "fault-proving")]
97 let registry_root = db
98 .registry_root()
99 .map_err(|e| anyhow::anyhow!("Failed to get registry root: {}", e))?;
100
101 Ok(VersionedCompressedBlock::new(
102 block.header(),
103 registrations,
104 transactions,
105 #[cfg(feature = "fault-proving")]
106 registry_root,
107 ))
108}
109
110struct PrepareCtx<'a, D> {
113 config: &'a Config,
114 timestamp: Tai64,
116 db: D,
118 accessed_keys: PerRegistryKeyspace<HashSet<RegistryKey>>,
120}
121
122impl<D> ContextError for PrepareCtx<'_, D> {
123 type Error = anyhow::Error;
124}
125
126impl<'a, D> CompressibleBy<PrepareCtx<'a, D>> for UtxoId
127where
128 D: CompressDb,
129{
130 async fn compress_with(
131 &self,
132 _ctx: &mut PrepareCtx<'a, D>,
133 ) -> anyhow::Result<CompressedUtxoId> {
134 Ok(CompressedUtxoId {
135 tx_pointer: TxPointer::default(),
136 output_index: 0,
137 })
138 }
139}
140
141#[derive(Debug)]
142struct CompressCtxKeyspace<T> {
143 cache_evictor: CacheEvictor<T>,
145 changes: HashMap<RegistryKey, T>,
147 changes_lookup: HashMap<T, RegistryKey>,
149}
150
151macro_rules! compression {
152 ($($ident:ty: $type:ty),*) => { paste::paste! {
153 pub struct CompressCtx<'a, D> {
154 config: &'a Config,
155 timestamp: Tai64,
156 db: D,
157 $($ident: CompressCtxKeyspace<$type>,)*
158 }
159
160 impl<'a, D> PrepareCtx<'a, D> where D: CompressDb {
161 pub fn into_compression_context(mut self) -> anyhow::Result<CompressCtx<'a, D>> {
165 Ok(CompressCtx {
166 $(
167 $ident: CompressCtxKeyspace {
168 changes: Default::default(),
169 changes_lookup: Default::default(),
170 cache_evictor: CacheEvictor::new_from_db(&mut self.db, self.accessed_keys.$ident.into())?,
171 },
172 )*
173 config: self.config,
174 timestamp: self.timestamp,
175 db: self.db,
176 })
177 }
178 }
179
180 impl<'a, D> CompressCtx<'a, D> where D: CompressDb {
181 fn finalize(mut self) -> anyhow::Result<RegistrationsPerTable> {
184 let mut registrations = RegistrationsPerTable::default();
185 $(
186 self.$ident.cache_evictor.commit(&mut self.db)?;
187 for (key, value) in self.$ident.changes.into_iter() {
188 registrations.$ident.push((key, value));
189 }
190 )*
191 registrations.write_to_registry(&mut self.db, self.timestamp)?;
192 Ok(registrations)
193 }
194 }
195
196 $(
197 impl<'a, D> CompressibleBy<PrepareCtx<'a, D>> for $type
198 where
199 D: TemporalRegistry<$type> + EvictorDb<$type>
200 {
201 async fn compress_with(
202 &self,
203 ctx: &mut PrepareCtx<'a, D>,
204 ) -> anyhow::Result<RegistryKey> {
205 if *self == <$type>::default() {
206 return Ok(RegistryKey::ZERO);
207 }
208 if let Some(found) = ctx.db.registry_index_lookup(self)? {
209 if !ctx.accessed_keys.$ident.contains(&found) {
210 let key_timestamp = ctx.db.read_timestamp(&found)
211 .context("Database invariant violated: no timestamp stored but key found")?;
212 if ctx.config.is_timestamp_accessible(ctx.timestamp, key_timestamp)? {
213 ctx.accessed_keys.$ident.insert(found);
214 }
215 }
216 }
217 Ok(RegistryKey::ZERO)
218 }
219 }
220
221 impl<'a, D> CompressibleBy<CompressCtx<'a, D>> for $type
222 where
223 D: TemporalRegistry<$type> + EvictorDb<$type>
224 {
225 async fn compress_with(
226 &self,
227 ctx: &mut CompressCtx<'a, D>,
228 ) -> anyhow::Result<RegistryKey> {
229 if self == &Default::default() {
230 return Ok(RegistryKey::DEFAULT_VALUE);
231 }
232 if let Some(found) = ctx.$ident.changes_lookup.get(self) {
233 return Ok(*found);
234 }
235 if let Some(found) = ctx.db.registry_index_lookup(self)? {
236 let key_timestamp = ctx.db.read_timestamp(&found)
237 .context("Database invariant violated: no timestamp stored but key found")?;
238 if ctx.config.is_timestamp_accessible(ctx.timestamp, key_timestamp)? {
239 return Ok(found);
240 }
241 }
242
243 let key = ctx.$ident.cache_evictor.next_key();
244 let old = ctx.$ident.changes.insert(key, self.clone());
245 let old_rev = ctx.$ident.changes_lookup.insert(self.clone(), key);
246 debug_assert!(old.is_none(), "Key collision in registry substitution");
247 debug_assert!(old_rev.is_none(), "Key collision in registry substitution");
248 Ok(key)
249 }
250 }
251 )*
252 }};
253}
254
255compression!(
256 address: Address,
257 asset_id: AssetId,
258 contract_id: ContractId,
259 script_code: ScriptCode,
260 predicate_code: PredicateCode
261);
262
263impl<D> ContextError for CompressCtx<'_, D> {
264 type Error = anyhow::Error;
265}
266
267impl<'a, D> CompressibleBy<CompressCtx<'a, D>> for UtxoId
268where
269 D: CompressDb,
270{
271 async fn compress_with(
272 &self,
273 ctx: &mut CompressCtx<'a, D>,
274 ) -> anyhow::Result<CompressedUtxoId> {
275 ctx.db.lookup(*self)
276 }
277}