fuel_core_compression/
decompress.rs

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
52/// This must be called for all decompressed blocks in sequence, otherwise the result will be garbage.
53pub async fn decompress<D>(
54    config: Config,
55    mut db: D,
56    block: VersionedCompressedBlock,
57) -> anyhow::Result<PartialFuelBlock>
58where
59    D: DecompressDb,
60{
61    // TODO: merkle root verification: https://github.com/FuelLabs/fuel-core/issues/2232
62
63    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    // patch mint transaction
82    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    /// Timestamp of the block being decompressed
105    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(), // TODO: what should we do with this?
234            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        // Given
342        let bad_block =
343            postcard::to_stdvec(&CompressedBlockWithNewVersions::NewVersion(1234))
344                .unwrap();
345
346        // When
347        let result: Result<VersionedCompressedBlock, _> =
348            postcard::from_bytes(&bad_block);
349
350        // Then
351        let _ =
352            result.expect_err("should fail to deserialize because of unknown version");
353    }
354}