1use crate::{
2 config::Config,
3 ports::{
4 HistoryLookup,
5 TemporalRegistry,
6 },
7 registry::TemporalRegistryAll,
8 VersionedBlockPayload,
9 VersionedCompressedBlock,
10};
11use fuel_core_types::{
12 blockchain::block::PartialFuelBlock,
13 fuel_compression::{
14 Compressible,
15 ContextError,
16 Decompress,
17 DecompressibleBy,
18 RegistryKey,
19 },
20 fuel_tx::{
21 field::TxPointer,
22 input::{
23 coin::{
24 Coin,
25 CoinSpecification,
26 },
27 message::{
28 Message,
29 MessageSpecification,
30 },
31 AsField,
32 PredicateCode,
33 },
34 AssetId,
35 CompressedUtxoId,
36 Mint,
37 ScriptCode,
38 Transaction,
39 TxPointer as FuelTxPointer,
40 UtxoId,
41 },
42 fuel_types::{
43 Address,
44 ContractId,
45 },
46 tai64::Tai64,
47};
48
49pub trait DecompressDb: TemporalRegistryAll + HistoryLookup {}
50impl<T> DecompressDb for T where T: TemporalRegistryAll + HistoryLookup {}
51
52pub async fn decompress<D>(
54 config: Config,
55 mut db: D,
56 block: VersionedCompressedBlock,
57) -> anyhow::Result<PartialFuelBlock>
58where
59 D: DecompressDb,
60{
61 block
64 .registrations()
65 .write_to_registry(&mut db, block.consensus_header().time)?;
66
67 let ctx = DecompressCtx {
68 config,
69 timestamp: block.consensus_header().time,
70 db,
71 };
72
73 let mut transactions = <Vec<Transaction> as DecompressibleBy<_>>::decompress_with(
74 block.transactions(),
75 &ctx,
76 )
77 .await?;
78
79 let transaction_count = transactions.len();
80
81 let mint_tx = transactions
83 .last_mut()
84 .ok_or_else(|| anyhow::anyhow!("No transactions"))?;
85 if let Transaction::Mint(mint) = mint_tx {
86 let tx_pointer = mint.tx_pointer_mut();
87 *tx_pointer = FuelTxPointer::new(
88 block.consensus_header().height,
89 #[allow(clippy::arithmetic_side_effects)]
90 u16::try_from(transaction_count - 1)?,
91 );
92 } else {
93 anyhow::bail!("Last transaction is not a mint");
94 }
95
96 Ok(PartialFuelBlock {
97 header: block.partial_block_header(),
98 transactions,
99 })
100}
101
102pub struct DecompressCtx<D> {
103 pub config: Config,
104 pub timestamp: Tai64,
106 pub db: D,
107}
108
109impl<D> ContextError for DecompressCtx<D> {
110 type Error = anyhow::Error;
111}
112
113impl<D> DecompressibleBy<DecompressCtx<D>> for UtxoId
114where
115 D: HistoryLookup,
116{
117 async fn decompress_with(
118 c: CompressedUtxoId,
119 ctx: &DecompressCtx<D>,
120 ) -> anyhow::Result<Self> {
121 ctx.db.utxo_id(c)
122 }
123}
124
125macro_rules! decompress_impl {
126 ($($type:ty),*) => { paste::paste! {
127 $(
128 impl<D> DecompressibleBy<DecompressCtx<D>> for $type
129 where
130 D: TemporalRegistry<$type>
131 {
132 async fn decompress_with(
133 key: RegistryKey,
134 ctx: &DecompressCtx<D>,
135 ) -> anyhow::Result<Self> {
136 if key == RegistryKey::DEFAULT_VALUE {
137 return Ok(<$type>::default());
138 }
139 let key_timestamp = ctx.db.read_timestamp(&key)?;
140 if !ctx.config.is_timestamp_accessible(ctx.timestamp, key_timestamp)? {
141 anyhow::bail!("Timestamp not accessible");
142 }
143 ctx.db.read_registry(&key)
144 }
145 }
146 )*
147 }};
148}
149
150decompress_impl!(AssetId, ContractId, Address, PredicateCode, ScriptCode);
151
152impl<D, Specification> DecompressibleBy<DecompressCtx<D>> for Coin<Specification>
153where
154 D: DecompressDb,
155 Specification: CoinSpecification,
156 Specification::Predicate: DecompressibleBy<DecompressCtx<D>>,
157 Specification::PredicateData: DecompressibleBy<DecompressCtx<D>>,
158 Specification::PredicateGasUsed: DecompressibleBy<DecompressCtx<D>>,
159 Specification::Witness: DecompressibleBy<DecompressCtx<D>>,
160{
161 async fn decompress_with(
162 c: <Coin<Specification> as Compressible>::Compressed,
163 ctx: &DecompressCtx<D>,
164 ) -> anyhow::Result<Coin<Specification>> {
165 let utxo_id = UtxoId::decompress_with(c.utxo_id, ctx).await?;
166 let coin_info = ctx.db.coin(utxo_id)?;
167 let witness_index = c.witness_index.decompress(ctx).await?;
168 let predicate_gas_used = c.predicate_gas_used.decompress(ctx).await?;
169 let predicate = c.predicate.decompress(ctx).await?;
170 let predicate_data = c.predicate_data.decompress(ctx).await?;
171 Ok(Self {
172 utxo_id,
173 owner: coin_info.owner,
174 amount: coin_info.amount,
175 asset_id: coin_info.asset_id,
176 tx_pointer: Default::default(),
177 witness_index,
178 predicate_gas_used,
179 predicate,
180 predicate_data,
181 })
182 }
183}
184
185impl<D, Specification> DecompressibleBy<DecompressCtx<D>> for Message<Specification>
186where
187 D: DecompressDb,
188 Specification: MessageSpecification,
189 Specification::Data: DecompressibleBy<DecompressCtx<D>> + Default,
190 Specification::Predicate: DecompressibleBy<DecompressCtx<D>>,
191 Specification::PredicateData: DecompressibleBy<DecompressCtx<D>>,
192 Specification::PredicateGasUsed: DecompressibleBy<DecompressCtx<D>>,
193 Specification::Witness: DecompressibleBy<DecompressCtx<D>>,
194{
195 async fn decompress_with(
196 c: <Message<Specification> as Compressible>::Compressed,
197 ctx: &DecompressCtx<D>,
198 ) -> anyhow::Result<Message<Specification>> {
199 let msg = ctx.db.message(c.nonce)?;
200 let witness_index = c.witness_index.decompress(ctx).await?;
201 let predicate_gas_used = c.predicate_gas_used.decompress(ctx).await?;
202 let predicate = c.predicate.decompress(ctx).await?;
203 let predicate_data = c.predicate_data.decompress(ctx).await?;
204 let mut message: Message<Specification> = Message {
205 sender: msg.sender,
206 recipient: msg.recipient,
207 amount: msg.amount,
208 nonce: c.nonce,
209 witness_index,
210 predicate_gas_used,
211 data: Default::default(),
212 predicate,
213 predicate_data,
214 };
215
216 if let Some(data) = message.data.as_mut_field() {
217 data.clone_from(&msg.data)
218 }
219
220 Ok(message)
221 }
222}
223
224impl<D> DecompressibleBy<DecompressCtx<D>> for Mint
225where
226 D: DecompressDb,
227{
228 async fn decompress_with(
229 c: Self::Compressed,
230 ctx: &DecompressCtx<D>,
231 ) -> anyhow::Result<Self> {
232 Ok(Transaction::mint(
233 Default::default(), c.input_contract.decompress(ctx).await?,
235 c.output_contract.decompress(ctx).await?,
236 c.mint_amount.decompress(ctx).await?,
237 c.mint_asset_id.decompress(ctx).await?,
238 c.gas_price.decompress(ctx).await?,
239 ))
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use crate::ports::{
246 EvictorDb,
247 TemporalRegistry,
248 };
249
250 use super::*;
251 use fuel_core_types::{
252 fuel_compression::RegistryKey,
253 fuel_tx::{
254 input::PredicateCode,
255 Address,
256 AssetId,
257 ContractId,
258 ScriptCode,
259 },
260 };
261 use serde::{
262 Deserialize,
263 Serialize,
264 };
265
266 pub struct MockDb;
267 impl HistoryLookup for MockDb {
268 fn utxo_id(&self, _: CompressedUtxoId) -> anyhow::Result<UtxoId> {
269 unimplemented!()
270 }
271
272 fn coin(&self, _: UtxoId) -> anyhow::Result<crate::ports::CoinInfo> {
273 unimplemented!()
274 }
275
276 fn message(
277 &self,
278 _: fuel_core_types::fuel_types::Nonce,
279 ) -> anyhow::Result<crate::ports::MessageInfo> {
280 unimplemented!()
281 }
282 }
283 macro_rules! mock_temporal {
284 ($type:ty) => {
285 impl TemporalRegistry<$type> for MockDb {
286 fn read_registry(&self, _key: &RegistryKey) -> anyhow::Result<$type> {
287 unimplemented!()
288 }
289
290 fn read_timestamp(&self, _key: &RegistryKey) -> anyhow::Result<Tai64> {
291 unimplemented!()
292 }
293
294 fn write_registry(
295 &mut self,
296 _key: &RegistryKey,
297 _value: &$type,
298 _timestamp: Tai64,
299 ) -> anyhow::Result<()> {
300 unimplemented!()
301 }
302
303 fn registry_index_lookup(
304 &self,
305 _value: &$type,
306 ) -> anyhow::Result<Option<RegistryKey>> {
307 unimplemented!()
308 }
309 }
310
311 impl EvictorDb<$type> for MockDb {
312 fn set_latest_assigned_key(
313 &mut self,
314 _key: RegistryKey,
315 ) -> anyhow::Result<()> {
316 unimplemented!()
317 }
318
319 fn get_latest_assigned_key(&self) -> anyhow::Result<Option<RegistryKey>> {
320 unimplemented!()
321 }
322 }
323 };
324 }
325 mock_temporal!(Address);
326 mock_temporal!(AssetId);
327 mock_temporal!(ContractId);
328 mock_temporal!(ScriptCode);
329 mock_temporal!(PredicateCode);
330
331 #[tokio::test]
332 async fn decompress_block_with_unknown_version() {
333 #[derive(Clone, Serialize, Deserialize)]
334 enum CompressedBlockWithNewVersions {
335 V0(crate::CompressedBlockPayloadV0),
336 NewVersion(u32),
337 #[serde(untagged)]
338 Unknown,
339 }
340
341 let bad_block =
343 postcard::to_stdvec(&CompressedBlockWithNewVersions::NewVersion(1234))
344 .unwrap();
345
346 let result: Result<VersionedCompressedBlock, _> =
348 postcard::from_bytes(&bad_block);
349
350 let _ =
352 result.expect_err("should fail to deserialize because of unknown version");
353 }
354}