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;
12mod 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 ) -> Self {
64 #[cfg(not(feature = "fault-proving"))]
65 return Self::V0(CompressedBlockPayloadV0::new(
66 header,
67 registrations,
68 transactions,
69 ));
70 #[cfg(feature = "fault-proving")]
71 Self::V1(CompressedBlockPayloadV1::new(
72 header,
73 registrations,
74 transactions,
75 ))
76 }
77}
78
79impl Default for VersionedCompressedBlock {
80 fn default() -> Self {
81 Self::V0(Default::default())
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use fuel_core_compression as _;
89 use fuel_core_types::{
90 blockchain::{
91 header::{
92 ApplicationHeader,
93 ConsensusHeader,
94 },
95 primitives::Empty,
96 },
97 fuel_compression::RegistryKey,
98 tai64::Tai64,
99 };
100 use proptest::prelude::*;
101
102 fn keyspace() -> impl Strategy<Value = RegistryKeyspace> {
103 prop_oneof![
104 Just(RegistryKeyspace::Address),
105 Just(RegistryKeyspace::AssetId),
106 Just(RegistryKeyspace::ContractId),
107 Just(RegistryKeyspace::ScriptCode),
108 Just(RegistryKeyspace::PredicateCode),
109 ]
110 }
111
112 #[derive(Debug)]
113 struct PostcardRoundtripStrategy {
114 da_height: u64,
115 prev_root: [u8; 32],
116 height: u32,
117 consensus_parameters_version: u32,
118 state_transition_bytecode_version: u32,
119 registrations: RegistrationsPerTable,
120 }
121
122 fn postcard_roundtrip_strategy() -> impl Strategy<Value = PostcardRoundtripStrategy> {
123 (
124 0..=u64::MAX,
125 prop::array::uniform32(0..=u8::MAX),
126 0..=u32::MAX,
127 0..=u32::MAX,
128 0..=u32::MAX,
129 prop::collection::vec(
130 (
131 keyspace(),
132 prop::num::u16::ANY,
133 prop::array::uniform32(0..=u8::MAX),
134 )
135 .prop_map(|(ks, rk, arr)| {
136 let k = RegistryKey::try_from(rk as u32).unwrap();
137 (ks, k, arr)
138 }),
139 0..123,
140 ),
141 )
142 .prop_map(
143 |(
144 da_height,
145 prev_root,
146 height,
147 consensus_parameters_version,
148 state_transition_bytecode_version,
149 registration_inputs,
150 )| {
151 let mut registrations: RegistrationsPerTable = Default::default();
152 for (ks, key, arr) in registration_inputs {
153 let value_len_limit = (key.as_u32() % 32) as usize;
154 match ks {
155 RegistryKeyspace::Address => {
156 registrations.address.push((key, arr.into()));
157 }
158 RegistryKeyspace::AssetId => {
159 registrations.asset_id.push((key, arr.into()));
160 }
161 RegistryKeyspace::ContractId => {
162 registrations.contract_id.push((key, arr.into()));
163 }
164 RegistryKeyspace::ScriptCode => {
165 registrations
166 .script_code
167 .push((key, arr[..value_len_limit].to_vec().into()));
168 }
169 RegistryKeyspace::PredicateCode => {
170 registrations
171 .predicate_code
172 .push((key, arr[..value_len_limit].to_vec().into()));
173 }
174 }
175 }
176
177 PostcardRoundtripStrategy {
178 da_height,
179 prev_root,
180 height,
181 consensus_parameters_version,
182 state_transition_bytecode_version,
183 registrations,
184 }
185 },
186 )
187 }
188
189 #[test]
192 fn postcard_roundtrip_v0() {
193 proptest!(|(strategy in postcard_roundtrip_strategy())| {
194 let PostcardRoundtripStrategy {
195 da_height,
196 prev_root,
197 height,
198 consensus_parameters_version,
199 state_transition_bytecode_version,
200 registrations,
201 } = strategy;
202
203 let header = PartialBlockHeader {
204 application: ApplicationHeader {
205 da_height: da_height.into(),
206 consensus_parameters_version,
207 state_transition_bytecode_version,
208 generated: Empty,
209 },
210 consensus: ConsensusHeader {
211 prev_root: prev_root.into(),
212 height: height.into(),
213 time: Tai64::UNIX_EPOCH,
214 generated: Empty
215 }
216 };
217
218 let original = VersionedCompressedBlock::V0(CompressedBlockPayloadV0 {
219 registrations,
220 header,
221 transactions: vec![],
222 });
223
224 let compressed = postcard::to_allocvec(&original).unwrap();
225 let decompressed: VersionedCompressedBlock =
226 postcard::from_bytes(&compressed).unwrap();
227
228 let consensus_header = decompressed.consensus_header();
229 let application_header = decompressed.application_header();
230
231 assert_eq!(decompressed.registrations(), original.registrations());
232
233 assert_eq!(application_header.da_height, da_height.into());
234 assert_eq!(consensus_header.prev_root, prev_root.into());
235 assert_eq!(consensus_header.height, height.into());
236 assert_eq!(application_header.consensus_parameters_version, consensus_parameters_version);
237 assert_eq!(application_header.state_transition_bytecode_version, state_transition_bytecode_version);
238
239 assert!(decompressed.transactions().is_empty());
240 });
241 }
242
243 #[cfg(feature = "fault-proving")]
244 #[test]
245 fn postcard_roundtrip_v1() {
246 use compressed_block_payload::v1::{
247 CompressedBlockHeader,
248 CompressedBlockPayloadV1,
249 };
250 use fuel_core_types::blockchain::primitives::BlockId;
251 use std::str::FromStr;
252
253 proptest!(|(strategy in postcard_roundtrip_strategy())| {
254 let PostcardRoundtripStrategy {
255 da_height,
256 prev_root,
257 height,
258 consensus_parameters_version,
259 state_transition_bytecode_version,
260 registrations,
261 } = strategy;
262
263 let header = CompressedBlockHeader {
264 application: ApplicationHeader {
265 da_height: da_height.into(),
266 consensus_parameters_version,
267 state_transition_bytecode_version,
268 generated: Empty,
269 },
270 consensus: ConsensusHeader {
271 prev_root: prev_root.into(),
272 height: height.into(),
273 time: Tai64::UNIX_EPOCH,
274 generated: Empty,
275 },
276 block_id: BlockId::from_str("0xecea85c17070bc2e65f911310dbd01198f4436052ebba96cded9ddf30c58dd1a").unwrap(),
277 };
278
279
280 let original = VersionedCompressedBlock::V1(CompressedBlockPayloadV1 {
281 header,
282 registrations,
283 transactions: vec![]
284 });
285
286 let compressed = postcard::to_allocvec(&original).unwrap();
287 let decompressed: VersionedCompressedBlock =
288 postcard::from_bytes(&compressed).unwrap();
289
290 let consensus_header = decompressed.consensus_header();
291 let application_header = decompressed.application_header();
292
293 assert_eq!(decompressed.registrations(), original.registrations());
294
295 assert_eq!(application_header.da_height, da_height.into());
296 assert_eq!(consensus_header.prev_root, prev_root.into());
297 assert_eq!(consensus_header.height, height.into());
298 assert_eq!(application_header.consensus_parameters_version, consensus_parameters_version);
299 assert_eq!(application_header.state_transition_bytecode_version, state_transition_bytecode_version);
300
301 assert!(decompressed.transactions().is_empty());
302
303 if let VersionedCompressedBlock::V1(block) = decompressed {
304 assert_eq!(block.header.block_id, header.block_id);
305 } else {
306 panic!("Expected V1 block, got {:?}", decompressed);
307 }
308 });
309 }
310}