1#![deny(clippy::arithmetic_side_effects)]
2#![deny(clippy::cast_possible_truncation)]
3#![deny(unused_crate_dependencies)]
4#![deny(warnings)]
5
6pub mod compress;
7mod compressed_block_payload;
8pub mod config;
9pub mod decompress;
10mod eviction_policy;
11pub mod ports;
12pub mod registry;
13
14pub use config::Config;
15use enum_dispatch::enum_dispatch;
16pub use registry::RegistryKeyspace;
17
18use crate::compressed_block_payload::v0::CompressedBlockPayloadV0;
19#[cfg(feature = "fault-proving")]
20use crate::compressed_block_payload::v1::CompressedBlockPayloadV1;
21use fuel_core_types::{
22 blockchain::{
23 header::{
24 ApplicationHeader,
25 BlockHeader,
26 ConsensusHeader,
27 PartialBlockHeader,
28 },
29 primitives::Empty,
30 },
31 fuel_tx::CompressedTransaction,
32 fuel_types::BlockHeight,
33};
34use registry::RegistrationsPerTable;
35
36#[enum_dispatch]
40pub trait VersionedBlockPayload {
41 fn height(&self) -> &BlockHeight;
42 fn consensus_header(&self) -> &ConsensusHeader<Empty>;
43 fn application_header(&self) -> &ApplicationHeader<Empty>;
44 fn registrations(&self) -> &RegistrationsPerTable;
45 fn transactions(&self) -> Vec<CompressedTransaction>;
46 fn partial_block_header(&self) -> PartialBlockHeader;
47}
48
49#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
51#[enum_dispatch(VersionedBlockPayload)]
52pub enum VersionedCompressedBlock {
53 V0(CompressedBlockPayloadV0),
54 #[cfg(feature = "fault-proving")]
55 V1(CompressedBlockPayloadV1),
56}
57
58impl VersionedCompressedBlock {
59 fn new(
60 header: &BlockHeader,
61 registrations: RegistrationsPerTable,
62 transactions: Vec<CompressedTransaction>,
63 #[cfg(feature = "fault-proving")]
64 registry_root: crate::compressed_block_payload::v1::RegistryRoot,
65 ) -> Self {
66 #[cfg(not(feature = "fault-proving"))]
67 return Self::V0(CompressedBlockPayloadV0::new(
68 header,
69 registrations,
70 transactions,
71 ));
72 #[cfg(feature = "fault-proving")]
73 Self::V1(CompressedBlockPayloadV1::new(
74 header,
75 registrations,
76 transactions,
77 registry_root,
78 ))
79 }
80}
81
82impl Default for VersionedCompressedBlock {
83 fn default() -> Self {
84 Self::V0(Default::default())
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use fuel_core_compression as _;
92 use fuel_core_types::{
93 blockchain::{
94 header::{
95 ApplicationHeader,
96 ConsensusHeader,
97 },
98 primitives::Empty,
99 },
100 fuel_compression::RegistryKey,
101 tai64::Tai64,
102 };
103 use proptest::prelude::*;
104
105 fn keyspace() -> impl Strategy<Value = RegistryKeyspace> {
106 prop_oneof![
107 Just(RegistryKeyspace::Address),
108 Just(RegistryKeyspace::AssetId),
109 Just(RegistryKeyspace::ContractId),
110 Just(RegistryKeyspace::ScriptCode),
111 Just(RegistryKeyspace::PredicateCode),
112 ]
113 }
114
115 #[derive(Debug)]
116 struct PostcardRoundtripStrategy {
117 da_height: u64,
118 prev_root: [u8; 32],
119 height: u32,
120 consensus_parameters_version: u32,
121 state_transition_bytecode_version: u32,
122 registrations: RegistrationsPerTable,
123 }
124
125 fn postcard_roundtrip_strategy() -> impl Strategy<Value = PostcardRoundtripStrategy> {
126 (
127 0..=u64::MAX,
128 prop::array::uniform32(0..=u8::MAX),
129 0..=u32::MAX,
130 0..=u32::MAX,
131 0..=u32::MAX,
132 prop::collection::vec(
133 (
134 keyspace(),
135 prop::num::u16::ANY,
136 prop::array::uniform32(0..=u8::MAX),
137 )
138 .prop_map(|(ks, rk, arr)| {
139 let k = RegistryKey::try_from(rk as u32).unwrap();
140 (ks, k, arr)
141 }),
142 0..123,
143 ),
144 )
145 .prop_map(
146 |(
147 da_height,
148 prev_root,
149 height,
150 consensus_parameters_version,
151 state_transition_bytecode_version,
152 registration_inputs,
153 )| {
154 let mut registrations: RegistrationsPerTable = Default::default();
155 for (ks, key, arr) in registration_inputs {
156 let value_len_limit = (key.as_u32() % 32) as usize;
157 match ks {
158 RegistryKeyspace::Address => {
159 registrations.address.push((key, arr.into()));
160 }
161 RegistryKeyspace::AssetId => {
162 registrations.asset_id.push((key, arr.into()));
163 }
164 RegistryKeyspace::ContractId => {
165 registrations.contract_id.push((key, arr.into()));
166 }
167 RegistryKeyspace::ScriptCode => {
168 registrations
169 .script_code
170 .push((key, arr[..value_len_limit].to_vec().into()));
171 }
172 RegistryKeyspace::PredicateCode => {
173 registrations
174 .predicate_code
175 .push((key, arr[..value_len_limit].to_vec().into()));
176 }
177 }
178 }
179
180 PostcardRoundtripStrategy {
181 da_height,
182 prev_root,
183 height,
184 consensus_parameters_version,
185 state_transition_bytecode_version,
186 registrations,
187 }
188 },
189 )
190 }
191
192 #[test]
195 fn postcard_roundtrip_v0() {
196 proptest!(|(strategy in postcard_roundtrip_strategy())| {
197 let PostcardRoundtripStrategy {
198 da_height,
199 prev_root,
200 height,
201 consensus_parameters_version,
202 state_transition_bytecode_version,
203 registrations,
204 } = strategy;
205
206 let header = PartialBlockHeader {
207 application: ApplicationHeader {
208 da_height: da_height.into(),
209 consensus_parameters_version,
210 state_transition_bytecode_version,
211 generated: Empty,
212 },
213 consensus: ConsensusHeader {
214 prev_root: prev_root.into(),
215 height: height.into(),
216 time: Tai64::UNIX_EPOCH,
217 generated: Empty
218 }
219 };
220
221 let original = VersionedCompressedBlock::V0(CompressedBlockPayloadV0 {
222 registrations,
223 header,
224 transactions: vec![],
225 });
226
227 let compressed = postcard::to_allocvec(&original).unwrap();
228 let decompressed: VersionedCompressedBlock =
229 postcard::from_bytes(&compressed).unwrap();
230
231 let consensus_header = decompressed.consensus_header();
232 let application_header = decompressed.application_header();
233
234 assert_eq!(decompressed.registrations(), original.registrations());
235
236 assert_eq!(application_header.da_height, da_height.into());
237 assert_eq!(consensus_header.prev_root, prev_root.into());
238 assert_eq!(consensus_header.height, height.into());
239 assert_eq!(application_header.consensus_parameters_version, consensus_parameters_version);
240 assert_eq!(application_header.state_transition_bytecode_version, state_transition_bytecode_version);
241
242 assert!(decompressed.transactions().is_empty());
243 });
244 }
245
246 #[cfg(feature = "fault-proving")]
247 #[test]
248 fn postcard_roundtrip_v1() {
249 use compressed_block_payload::v1::{
250 CompressedBlockHeader,
251 CompressedBlockPayloadV1,
252 };
253 use fuel_core_types::blockchain::primitives::BlockId;
254 use std::str::FromStr;
255
256 use crate::compressed_block_payload::v1::RegistryRoot;
257
258 proptest!(|(strategy in postcard_roundtrip_strategy())| {
259 let PostcardRoundtripStrategy {
260 da_height,
261 prev_root,
262 height,
263 consensus_parameters_version,
264 state_transition_bytecode_version,
265 registrations,
266 } = strategy;
267
268 let header = CompressedBlockHeader {
269 application: ApplicationHeader {
270 da_height: da_height.into(),
271 consensus_parameters_version,
272 state_transition_bytecode_version,
273 generated: Empty,
274 },
275 consensus: ConsensusHeader {
276 prev_root: prev_root.into(),
277 height: height.into(),
278 time: Tai64::UNIX_EPOCH,
279 generated: Empty,
280 },
281 block_id: BlockId::from_str("0xecea85c17070bc2e65f911310dbd01198f4436052ebba96cded9ddf30c58dd1a").unwrap(),
282 registry_root: RegistryRoot::from_str("0xecea85c17070bc2e65f911310dbd01198f4436052ebba96cded9ddf30c58dd1b").unwrap(),
283 };
284
285
286 let original = VersionedCompressedBlock::V1(CompressedBlockPayloadV1 {
287 header,
288 registrations,
289 transactions: vec![]
290 });
291
292 let compressed = postcard::to_allocvec(&original).unwrap();
293 let decompressed: VersionedCompressedBlock =
294 postcard::from_bytes(&compressed).unwrap();
295
296 let consensus_header = decompressed.consensus_header();
297 let application_header = decompressed.application_header();
298
299 assert_eq!(decompressed.registrations(), original.registrations());
300
301 assert_eq!(application_header.da_height, da_height.into());
302 assert_eq!(consensus_header.prev_root, prev_root.into());
303 assert_eq!(consensus_header.height, height.into());
304 assert_eq!(application_header.consensus_parameters_version, consensus_parameters_version);
305 assert_eq!(application_header.state_transition_bytecode_version, state_transition_bytecode_version);
306
307 assert!(decompressed.transactions().is_empty());
308
309 if let VersionedCompressedBlock::V1(block) = decompressed {
310 assert_eq!(block.header.block_id, header.block_id);
311 assert_eq!(block.header.registry_root, header.registry_root);
312 } else {
313 panic!("Expected V1 block, got {:?}", decompressed);
314 }
315 });
316 }
317}