1use super::{ext::Ext, *};
21use crate::ext::StorageAppend;
22use arbitrary::Arbitrary;
23#[cfg(test)]
24use codec::Encode;
25use hash_db::Hasher;
26use sp_core::{storage::StateVersion, traits::Externalities};
27#[cfg(test)]
28use sp_runtime::traits::BlakeTwo256;
29use sp_trie::PrefixedMemoryDB;
30use std::collections::BTreeMap;
31
32#[derive(Arbitrary, Debug, Clone)]
33enum DataLength {
34 Zero = 0,
35 Small = 1,
36 Medium = 3,
37 Big = 300, }
39
40#[derive(Arbitrary, Debug, Clone)]
41#[repr(u8)]
42enum DataValue {
43 A = b'a',
44 B = b'b',
45 C = b'c',
46 D = b'd', EasyBug = 20u8, }
49
50#[derive(Arbitrary, Debug, Clone)]
52enum FuzzAppendItem {
53 Append(DataValue, DataLength),
54 Insert(DataValue, DataLength),
55 StartTransaction,
56 RollbackTransaction,
57 CommitTransaction,
58 Read,
59 Remove,
60 Append50(DataValue, DataLength),
62}
63
64#[derive(Arbitrary, Debug, Clone)]
66pub struct FuzzAppendPayload(Vec<FuzzAppendItem>, Option<(DataValue, DataLength)>);
67
68struct SimpleOverlay {
69 data: Vec<BTreeMap<Vec<u8>, Option<Vec<u8>>>>,
70}
71
72impl Default for SimpleOverlay {
73 fn default() -> Self {
74 Self { data: vec![BTreeMap::new()] }
75 }
76}
77
78impl SimpleOverlay {
79 fn insert(&mut self, key: Vec<u8>, value: Option<Vec<u8>>) {
80 self.data.last_mut().expect("always at least one item").insert(key, value);
81 }
82
83 fn append<H>(
84 &mut self,
85 key: Vec<u8>,
86 value: Vec<u8>,
87 backend: &mut TrieBackend<PrefixedMemoryDB<H>, H>,
88 ) where
89 H: Hasher,
90 H::Out: codec::Decode + codec::Encode + 'static,
91 {
92 let current_value = self
93 .data
94 .last_mut()
95 .expect("always at least one item")
96 .entry(key.clone())
97 .or_insert_with(|| {
98 Some(backend.storage(&key).expect("Ext not allowed to fail").unwrap_or_default())
99 });
100 if current_value.is_none() {
101 *current_value = Some(vec![]);
102 }
103 StorageAppend::new(current_value.as_mut().expect("init above")).append(value);
104 }
105
106 fn get(&mut self, key: &[u8]) -> Option<&Vec<u8>> {
107 self.data
108 .last_mut()
109 .expect("always at least one item")
110 .get(key)
111 .and_then(|o| o.as_ref())
112 }
113
114 fn commit_transaction(&mut self) {
115 if let Some(to_commit) = self.data.pop() {
116 let dest = self.data.last_mut().expect("always at least one item");
117 for (k, v) in to_commit.into_iter() {
118 dest.insert(k, v);
119 }
120 }
121 }
122
123 fn rollback_transaction(&mut self) {
124 let _ = self.data.pop();
125 }
126
127 fn start_transaction(&mut self) {
128 let cloned = self.data.last().expect("always at least one item").clone();
129 self.data.push(cloned);
130 }
131}
132
133struct FuzzAppendState<H: Hasher> {
134 key: Vec<u8>,
135
136 reference: SimpleOverlay,
138
139 backend: TrieBackend<PrefixedMemoryDB<H>, H>,
141 overlay: OverlayedChanges<H>,
143
144 transaction_depth: usize,
146}
147
148impl<H> FuzzAppendState<H>
149where
150 H: Hasher,
151 H::Out: codec::Decode + codec::Encode + 'static,
152{
153 fn process_item(&mut self, item: FuzzAppendItem) {
154 let mut ext = Ext::new(&mut self.overlay, &mut self.backend, None, StateVersion::default());
155 match item {
156 FuzzAppendItem::Append(value, length) => {
157 let value = vec![value as u8; length as usize];
158 ext.storage_append(self.key.clone(), value.clone());
159 self.reference.append(self.key.clone(), value, &mut self.backend);
160 },
161 FuzzAppendItem::Append50(value, length) => {
162 let value = vec![value as u8; length as usize];
163 for _ in 0..50 {
164 let mut ext = Ext::new(
165 &mut self.overlay,
166 &mut self.backend,
167 None,
168 StateVersion::default(),
169 );
170 ext.storage_append(self.key.clone(), value.clone());
171 self.reference.append(self.key.clone(), value.clone(), &mut self.backend);
172 }
173 },
174 FuzzAppendItem::Insert(value, length) => {
175 let value = vec![value as u8; length as usize];
176 ext.set_storage(self.key.clone(), value.clone());
177 self.reference.insert(self.key.clone(), Some(value));
178 },
179 FuzzAppendItem::Remove => {
180 ext.clear_storage(&self.key);
181 self.reference.insert(self.key.clone(), None);
182 },
183 FuzzAppendItem::Read => {
184 let left = ext.storage(self.key.as_slice());
185 let right = self.reference.get(self.key.as_slice());
186 assert_eq!(left.as_ref(), right);
187 },
188 FuzzAppendItem::StartTransaction => {
189 self.transaction_depth += 1;
190 self.reference.start_transaction();
191 ext.storage_start_transaction();
192 },
193 FuzzAppendItem::RollbackTransaction => {
194 if self.transaction_depth == 0 {
195 return;
196 }
197 self.transaction_depth -= 1;
198 self.reference.rollback_transaction();
199 ext.storage_rollback_transaction().unwrap();
200 },
201 FuzzAppendItem::CommitTransaction => {
202 if self.transaction_depth == 0 {
203 return;
204 }
205 self.transaction_depth -= 1;
206 self.reference.commit_transaction();
207 ext.storage_commit_transaction().unwrap();
208 },
209 }
210 }
211
212 fn check_final_state(&mut self) {
213 let mut ext = Ext::new(&mut self.overlay, &mut self.backend, None, StateVersion::default());
214 let left = ext.storage(self.key.as_slice());
215 let right = self.reference.get(self.key.as_slice());
216 assert_eq!(left.as_ref(), right);
217 }
218}
219
220#[test]
221fn fuzz_scenarii() {
222 assert_eq!(codec::Compact(5u16).encode()[0], DataValue::EasyBug as u8);
223 let scenarii = vec![
224 (
225 vec![
226 FuzzAppendItem::Append(DataValue::A, DataLength::Small),
227 FuzzAppendItem::StartTransaction,
228 FuzzAppendItem::Append50(DataValue::D, DataLength::Small),
229 FuzzAppendItem::Read,
230 FuzzAppendItem::RollbackTransaction,
231 FuzzAppendItem::StartTransaction,
232 FuzzAppendItem::Append(DataValue::D, DataLength::Small),
233 FuzzAppendItem::Read,
234 FuzzAppendItem::RollbackTransaction,
235 ],
236 Some((DataValue::D, DataLength::Small)),
237 ),
238 (
239 vec![
240 FuzzAppendItem::Append(DataValue::B, DataLength::Small),
241 FuzzAppendItem::StartTransaction,
242 FuzzAppendItem::Append(DataValue::A, DataLength::Small),
243 FuzzAppendItem::StartTransaction,
244 FuzzAppendItem::Remove,
245 FuzzAppendItem::StartTransaction,
246 FuzzAppendItem::Append(DataValue::A, DataLength::Zero),
247 FuzzAppendItem::CommitTransaction,
248 FuzzAppendItem::CommitTransaction,
249 FuzzAppendItem::Remove,
250 ],
251 Some((DataValue::EasyBug, DataLength::Small)),
252 ),
253 (
254 vec![
255 FuzzAppendItem::Append(DataValue::A, DataLength::Small),
256 FuzzAppendItem::StartTransaction,
257 FuzzAppendItem::Append(DataValue::A, DataLength::Medium),
258 FuzzAppendItem::StartTransaction,
259 FuzzAppendItem::Remove,
260 FuzzAppendItem::CommitTransaction,
261 FuzzAppendItem::RollbackTransaction,
262 ],
263 Some((DataValue::B, DataLength::Big)),
264 ),
265 (
266 vec![
267 FuzzAppendItem::Append(DataValue::A, DataLength::Big),
268 FuzzAppendItem::StartTransaction,
269 FuzzAppendItem::Append(DataValue::A, DataLength::Medium),
270 FuzzAppendItem::Remove,
271 FuzzAppendItem::RollbackTransaction,
272 FuzzAppendItem::StartTransaction,
273 FuzzAppendItem::Append(DataValue::A, DataLength::Zero),
274 ],
275 None,
276 ),
277 (
278 vec![
279 FuzzAppendItem::StartTransaction,
280 FuzzAppendItem::RollbackTransaction,
281 FuzzAppendItem::RollbackTransaction,
282 FuzzAppendItem::Append(DataValue::A, DataLength::Zero),
283 ],
284 None,
285 ),
286 (vec![FuzzAppendItem::StartTransaction], Some((DataValue::EasyBug, DataLength::Zero))),
287 ];
288
289 for (scenario, init) in scenarii.into_iter() {
290 fuzz_append::<BlakeTwo256>(FuzzAppendPayload(scenario, init));
291 }
292}
293
294pub fn fuzz_append<H>(payload: FuzzAppendPayload)
296where
297 H: Hasher,
298 H::Out: codec::Decode + codec::Encode + 'static,
299{
300 let FuzzAppendPayload(to_fuzz, initial) = payload;
301 let key = b"k".to_vec();
302 let mut reference = SimpleOverlay::default();
303 let initial: BTreeMap<_, _> = initial
304 .into_iter()
305 .map(|(v, l)| (key.clone(), vec![v as u8; l as usize]))
306 .collect();
307 for (k, v) in initial.iter() {
308 reference.data[0].insert(k.clone(), Some(v.clone()));
309 }
310 reference.start_transaction(); let overlay = OverlayedChanges::default();
312
313 let mut state = FuzzAppendState::<H> {
314 key,
315 reference,
316 overlay,
317 backend: (initial, StateVersion::default()).into(),
318 transaction_depth: 0,
319 };
320 for item in to_fuzz {
321 state.process_item(item);
322 }
323 state.check_final_state();
324}