Skip to main content

fuel_core_compression/
decompress.rs

1use crate::{
2    VersionedBlockPayload,
3    VersionedCompressedBlock,
4    config::Config,
5    ports::{
6        HistoryLookup,
7        TemporalRegistry,
8    },
9    registry::TemporalRegistryAll,
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        AssetId,
22        CompressedUtxoId,
23        Mint,
24        ScriptCode,
25        Transaction,
26        TxPointer as FuelTxPointer,
27        UtxoId,
28        field::TxPointer,
29        input::{
30            AsField,
31            PredicateCode,
32            coin::{
33                Coin,
34                CoinSpecification,
35            },
36            message::{
37                Message,
38                MessageSpecification,
39            },
40        },
41    },
42    fuel_types::{
43        Address,
44        ContractId,
45    },
46    tai64::Tai64,
47};
48
49#[cfg(not(feature = "fault-proving"))]
50pub mod not_fault_proving {
51    use super::*;
52    pub trait DecompressDb: TemporalRegistryAll + HistoryLookup {}
53    impl<T> DecompressDb for T where T: TemporalRegistryAll + HistoryLookup {}
54}
55
56#[cfg(feature = "fault-proving")]
57pub mod fault_proving {
58    use super::*;
59    use crate::ports::GetRegistryRoot;
60    pub trait DecompressDb:
61        TemporalRegistryAll + HistoryLookup + GetRegistryRoot
62    {
63    }
64    impl<T> DecompressDb for T where T: TemporalRegistryAll + HistoryLookup + GetRegistryRoot {}
65}
66
67#[cfg(feature = "fault-proving")]
68use fault_proving::DecompressDb;
69use fuel_core_types::fuel_types::bytes::Bytes;
70#[cfg(not(feature = "fault-proving"))]
71use not_fault_proving::DecompressDb;
72
73/// This must be called for all decompressed blocks in sequence, otherwise the result will be garbage.
74pub async fn decompress<D>(
75    config: Config,
76    mut db: D,
77    block: VersionedCompressedBlock,
78) -> anyhow::Result<PartialFuelBlock>
79where
80    D: DecompressDb,
81{
82    block
83        .registrations()
84        .write_to_registry(&mut db, block.consensus_header().time)?;
85
86    let ctx = DecompressCtx {
87        config,
88        timestamp: block.consensus_header().time,
89        db,
90    };
91
92    let mut transactions = <Vec<Transaction> as DecompressibleBy<_>>::decompress_with(
93        block.transactions(),
94        &ctx,
95    )
96    .await?;
97
98    let transaction_count = transactions.len();
99
100    // patch mint transaction
101    let mint_tx = transactions
102        .last_mut()
103        .ok_or_else(|| anyhow::anyhow!("No transactions"))?;
104    if let Transaction::Mint(mint) = mint_tx {
105        let tx_pointer = mint.tx_pointer_mut();
106        *tx_pointer = FuelTxPointer::new(
107            block.consensus_header().height,
108            #[allow(clippy::arithmetic_side_effects)]
109            u16::try_from(transaction_count - 1)?,
110        );
111    } else {
112        anyhow::bail!("Last transaction is not a mint");
113    }
114
115    #[cfg(feature = "fault-proving")]
116    {
117        match block {
118            VersionedCompressedBlock::V0(_) => {}
119            VersionedCompressedBlock::V1(ref block) => {
120                let registry_root_after_decompression = ctx
121                    .db
122                    .registry_root()
123                    .map_err(|e| anyhow::anyhow!("Failed to get registry root: {}", e))?;
124                let registry_root_after_compression = block.header.registry_root;
125                if registry_root_after_decompression != registry_root_after_compression {
126                    anyhow::bail!(
127                        "Registry root mismatch. registry root after decompression: {:?}, registry root after compression: {:?}",
128                        registry_root_after_decompression,
129                        registry_root_after_compression
130                    );
131                }
132            }
133        }
134    }
135
136    Ok(PartialFuelBlock {
137        header: block.partial_block_header(),
138        transactions,
139    })
140}
141
142pub struct DecompressCtx<D> {
143    pub config: Config,
144    /// Timestamp of the block being decompressed
145    pub timestamp: Tai64,
146    pub db: D,
147}
148
149impl<D> ContextError for DecompressCtx<D> {
150    type Error = anyhow::Error;
151}
152
153impl<D> DecompressibleBy<DecompressCtx<D>> for UtxoId
154where
155    D: HistoryLookup,
156{
157    async fn decompress_with(
158        c: CompressedUtxoId,
159        ctx: &DecompressCtx<D>,
160    ) -> anyhow::Result<Self> {
161        ctx.db.utxo_id(c)
162    }
163}
164
165macro_rules! decompress_impl {
166    ($($type:ty),*) => { paste::paste! {
167        $(
168            impl<D> DecompressibleBy<DecompressCtx<D>> for $type
169            where
170                D: TemporalRegistry<$type>
171            {
172                async fn decompress_with(
173                    key: RegistryKey,
174                    ctx: &DecompressCtx<D>,
175                ) -> anyhow::Result<Self> {
176                    if key == RegistryKey::DEFAULT_VALUE {
177                        return Ok(<$type>::default());
178                    }
179                    let key_timestamp = ctx.db.read_timestamp(&key)?;
180                    if !ctx.config.is_timestamp_accessible(ctx.timestamp, key_timestamp)? {
181                        anyhow::bail!("Timestamp not accessible");
182                    }
183                    ctx.db.read_registry(&key)
184                }
185            }
186        )*
187    }};
188}
189
190decompress_impl!(AssetId, ContractId, Address, PredicateCode, ScriptCode);
191
192impl<D, Specification> DecompressibleBy<DecompressCtx<D>> for Coin<Specification>
193where
194    D: DecompressDb,
195    Specification: CoinSpecification,
196    Specification::Predicate: DecompressibleBy<DecompressCtx<D>>,
197    Specification::PredicateData: DecompressibleBy<DecompressCtx<D>>,
198    Specification::PredicateGasUsed: DecompressibleBy<DecompressCtx<D>>,
199    Specification::Witness: DecompressibleBy<DecompressCtx<D>>,
200{
201    async fn decompress_with(
202        c: <Coin<Specification> as Compressible>::Compressed,
203        ctx: &DecompressCtx<D>,
204    ) -> anyhow::Result<Coin<Specification>> {
205        let utxo_id = UtxoId::decompress_with(c.utxo_id, ctx).await?;
206        let coin_info = ctx.db.coin(utxo_id)?;
207        let witness_index = c.witness_index.decompress(ctx).await?;
208        let predicate_gas_used = c.predicate_gas_used.decompress(ctx).await?;
209        let predicate = c.predicate.decompress(ctx).await?;
210        let predicate_data = c.predicate_data.decompress(ctx).await?;
211        Ok(Self {
212            utxo_id,
213            owner: coin_info.owner,
214            amount: coin_info.amount,
215            asset_id: coin_info.asset_id,
216            tx_pointer: Default::default(),
217            witness_index,
218            predicate_gas_used,
219            predicate,
220            predicate_data,
221        })
222    }
223}
224
225impl<D, Specification> DecompressibleBy<DecompressCtx<D>> for Message<Specification>
226where
227    D: DecompressDb,
228    Specification: MessageSpecification,
229    Specification::Data: DecompressibleBy<DecompressCtx<D>> + Default,
230    Specification::Predicate: DecompressibleBy<DecompressCtx<D>>,
231    Specification::PredicateData: DecompressibleBy<DecompressCtx<D>>,
232    Specification::PredicateGasUsed: DecompressibleBy<DecompressCtx<D>>,
233    Specification::Witness: DecompressibleBy<DecompressCtx<D>>,
234{
235    async fn decompress_with(
236        c: <Message<Specification> as Compressible>::Compressed,
237        ctx: &DecompressCtx<D>,
238    ) -> anyhow::Result<Message<Specification>> {
239        let msg = ctx.db.message(c.nonce)?;
240        let witness_index = c.witness_index.decompress(ctx).await?;
241        let predicate_gas_used = c.predicate_gas_used.decompress(ctx).await?;
242        let predicate = c.predicate.decompress(ctx).await?;
243        let predicate_data = c.predicate_data.decompress(ctx).await?;
244        let mut message: Message<Specification> = Message {
245            sender: msg.sender,
246            recipient: msg.recipient,
247            amount: msg.amount,
248            nonce: c.nonce,
249            witness_index,
250            predicate_gas_used,
251            data: Default::default(),
252            predicate,
253            predicate_data,
254        };
255
256        if let Some(data) = message.data.as_mut_field() {
257            *data = Bytes::new(msg.data.clone());
258        }
259
260        Ok(message)
261    }
262}
263
264impl<D> DecompressibleBy<DecompressCtx<D>> for Mint
265where
266    D: DecompressDb,
267{
268    async fn decompress_with(
269        c: Self::Compressed,
270        ctx: &DecompressCtx<D>,
271    ) -> anyhow::Result<Self> {
272        Ok(Transaction::mint(
273            Default::default(), // TODO: what should we do with this?
274            c.input_contract.decompress(ctx).await?,
275            c.output_contract.decompress(ctx).await?,
276            c.mint_amount.decompress(ctx).await?,
277            c.mint_asset_id.decompress(ctx).await?,
278            c.gas_price.decompress(ctx).await?,
279        ))
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286    use serde::{
287        Deserialize,
288        Serialize,
289    };
290
291    #[tokio::test]
292    async fn decompress_block_with_unknown_version() {
293        #[derive(Clone, Serialize, Deserialize)]
294        #[allow(clippy::large_enum_variant)]
295        enum CompressedBlockWithNewVersions {
296            V0(crate::CompressedBlockPayloadV0),
297            NewVersion(u32),
298            #[serde(untagged)]
299            Unknown,
300        }
301
302        // Given
303        let bad_block =
304            postcard::to_stdvec(&CompressedBlockWithNewVersions::NewVersion(1234))
305                .unwrap();
306
307        // When
308        let result: Result<VersionedCompressedBlock, _> =
309            postcard::from_bytes(&bad_block);
310
311        // Then
312        let _ =
313            result.expect_err("should fail to deserialize because of unknown version");
314    }
315}