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
73pub 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 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 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(), 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 let bad_block =
304 postcard::to_stdvec(&CompressedBlockWithNewVersions::NewVersion(1234))
305 .unwrap();
306
307 let result: Result<VersionedCompressedBlock, _> =
309 postcard::from_bytes(&bad_block);
310
311 let _ =
313 result.expect_err("should fail to deserialize because of unknown version");
314 }
315}