Skip to content

Commit c087d20

Browse files
author
José Molina Colmenero
authored
Improve unbounded storage iteration (#16)
* Improve unbounded storage iteration * Improve storage growth * Add extra section about try_append
1 parent 6982ec2 commit c087d20

5 files changed

Lines changed: 74 additions & 19 deletions

File tree

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
- [Medium Severity](medium/README.md)
3131

32+
- [Append entries efficiently](medium/Append_Entries_Efficiently.md)
3233
- [Remove deprecated storage getters](medium/Remove_Deprecated_Storage_Getters.md)
3334
- [Avoid hardcoded parameters and values](medium/Avoid_Hardcoded_Parameters_and_Values.md)
3435
- [Include tests for edge cases](medium/Include_Tests_for_Edge_Cases.md)

src/TABLE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
| <span style="color:red;">Critical</span> | Use appropriate origin checks | Open access on extrinsics without checks may allow unauthorized actions that can compromise security | Add access control checks to limit access to specific users or roles |
55
| <span style="color:red;">Critical</span> | Avoid unbounded iteration | Unbounded iterations over large data structures can lead to resource exhaustion and potential denial of service | Implement limits or use a bounded storage map for these iterations |
66
| <span style="color:red;">Critical</span> | Unchecked input data | Lack of input validation can lead to unexpected behaviors and potential vulnerabilities | Validate input data before processing to ensure safe and predictable behavior |
7-
| <span style="color:red;">Critical</span> | Avoid unwrap usage inside runtime | Using `unwrap()` or `expect()` without proper error handling can lead to runtime panics and crashes | Handle errors gracefully with `Result` or `Option` types to prevent panics |
7+
| <span style="color:red;">Critical</span> | Avoid unwrap usage inside runtime | Using `unwrap()` or `expect()` without proper error handling can lead to runtime panics and crashes | Handle errors gracefully with `Result` or `Option` types to prevent panics |
88
| <span style="color:red;">Critical</span> | Use benchmarking for accurate dynamic weights | Using hardcoded weights for extrinsics can lead to inaccurate resource estimations and performance issues. | Implement benchmarking to dynamically assess the weights of functions, ensuring they accurately reflect execution costs |
99
| <span style="color:orange;">High</span> | Make proper usage of XCM `Junctions` | Misuse of junction types (especially GeneralIndex) for purposes beyond their intended entity representation can lead to incorrect path routing | Use junctions strictly for their intended purpose of representing entities in Location paths; propose RFCs for new needs |
1010
| <span style="color:orange;">High</span> | Properly setup XCM `Barrier` | Improperly configured XCM executor barriers can allow unauthorized free execution from any origin | Implement restrictive barriers with explicit authorization for unpaid execution and clear documentation of intended uses |
@@ -19,6 +19,7 @@
1919
| <span style="color:orange;">High</span> | Avoid redundant storage access in mutations | Using both try_mutate and insert leads to unnecessary storage accesses | Use `try_mutate` or `try_mutate_exists` to read, modify, and write in a single step |
2020
| <span style="color:orange;">High</span> | Prevent unnecessary reads and writes in storage access | Frequent reads and writes to storage without optimization can degrade performance | Use efficient storage access methods such as `try_mutate` to combine reads and writes |
2121
| <span style="color:orange;">High</span> | Implement `try-state` Hook | The absence of `try-state` hooks prevents runtime sanity checks, making it harder to ensure that the storage state is sensible after upgrades | Implement the `try-state` hook to perform thorough state checks without altering storage |
22+
| <span style="color:gold;">Medium</span> | Append entries efficiently | Using `try_mutate` has a severe penalty when appending elements to a `StorageValue` | Use `try_append` instead of `try_mutate` whenever possible |
2223
| <span style="color:gold;">Medium</span> | Implement proper XCM fee management | Using the FeeManager unit type without consideration leads to unintended fee burning rather than proper fee handling | Implement proper FeeManager that either deposits or distributes fees, with clear handling of fee-exempt locations |
2324
| <span style="color:gold;">Medium</span> | Remove deprecated storage getters | Using deprecated storage getters may lead to compatibility issues in future versions | Replace deprecated getters with the recommended methods in updated frameworks |
2425
| <span style="color:gold;">Medium</span> | Avoid hardcoded parameters and values | Hardcoding parameters can reduce flexibility and adaptability to different environments | Use configurable parameters to enhance adaptability |

src/critical/Avoid_Unbounded_Iteration.md

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ The following example illustrates an iteration over all items in `big_data_set`
1212

1313
```rust
1414
#[pallet::storage]
15-
pub type UnboundedData<T: Config> = StorageValue<_, Vec<u32>>;
15+
pub type UnboundedData<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, u32>;
1616

17-
let big_data_set = UnboundedData::<T>::get();
18-
for item in big_data_set {
19-
// Process each item with no limit
17+
fn iterate_over_data() {
18+
let big_data_set = UnboundedData::<T>::iter();
19+
for (acc, data) in big_data_set {
20+
// Process each item with no limit
21+
}
2022
}
2123
```
2224

@@ -30,11 +32,13 @@ Use a bounded iterator or limit the number of items processed in each iteration.
3032
const MAX_ITEMS: usize = 20;
3133

3234
#[pallet::storage]
33-
pub type UnboundedData<T: Config> = StorageValue<_, Vec<u32>>;
35+
pub type UnboundedData<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, u32>;
3436

35-
let big_data = UnboundedData::<T>::get();
36-
for item in big_data.iter().take(MAX_ITEMS) {
37-
// Process a limited number of items safely
37+
fn iterate_over_data() {
38+
let big_data = UnboundedData::<T>::iter();
39+
for (acc, data) in big_data.take(MAX_ITEMS) {
40+
// Process a limited number of items safely
41+
}
3842
}
3943
```
4044

@@ -46,11 +50,13 @@ Alternatively, use bounded data structures to enforce strict size limits at the
4650

4751
```rust
4852
#[pallet::storage]
49-
pub type BoundedData<T: Config> = StorageValue<_, BoundedVec<u32, T::MaxEntries>>;
53+
pub type BoundedData<T: Config> = StorageValue<_, BoundedBTreeMap<T::AccountId, u32, T::MaxEntries>>;
5054

51-
let bounded_data = BoundedData::<T>::get();
52-
for item in bounded_data {
53-
// Iterates over a data structure with bounded size.
55+
fn iterate_over_data() {
56+
let bounded_data = BoundedData::<T>::get();
57+
for (acc, data) in bounded_data {
58+
// Iterates over a data structure with bounded size.
59+
}
5460
}
5561
```
5662

src/high/Be_Careful_With_Storage_Growth.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The following code allows adding entries without any limit, leading to uncontrol
1212

1313
```rust
1414
#[pallet::storage]
15+
#[pallet::unbounded]
1516
pub type Entries<T: Config> = StorageValue<_, Vec<u32>>;
1617

1718
fn add_entry(entry: u32) {
@@ -32,15 +33,13 @@ pub type Entries<T: Config> = StorageValue<_, BoundedVec<u32, T::MaxEntries>>;
3233

3334
#[pallet::error]
3435
pub enum Error<T> {
35-
/// MaxEntries limit reached
36+
/// MaxEntries limit reached.
3637
TooManyEntries,
3738
}
3839

39-
fn add_entry_limited(entry: u32) -> Result<(), Error> {
40-
Entries::<T>::try_mutate(|entries| {
41-
entries.try_push(entry).map_err(|_| Error::<T>::TooManyEntries)?;
42-
Ok(())
43-
})
40+
fn add_entry_limited(entry: u32) -> Result<(), DispatchError> {
41+
Entries::<T>::try_append(entry).map_err(|_| Error::<T>::TooManyEntries)?;
42+
Ok(())
4443
}
4544
```
4645

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Append Entries Efficiently
2+
3+
**Severity**: <span style="color:gold;">Medium</span>
4+
5+
## Description
6+
7+
Using `try_append` instead of `try_mutate` for a Substrate `StorageValue` has several advantages. Specifically,
8+
`try_append` is more efficient because it avoids reading and rewriting the entire storage value, resulting in lower
9+
computation and storage costs when appending a single item. This makes it particularly useful for operations where the
10+
goal is simply to append an entry without other modifications. By contrast, `try_mutate` reads the entire storage value
11+
into memory, modifies it, and writes it back, which can be both computationally and storage-intensive for large
12+
collections.
13+
14+
## What should be avoided
15+
16+
Using `try_mutate` has a severe penalty when compared to `try_append` and should be avoided whenever possible.
17+
18+
```rust
19+
#[pallet::storage]
20+
pub type Entries<T: Config> = StorageValue<_, BoundedVec<u32, T::MaxEntries>>;
21+
22+
#[pallet::error]
23+
pub enum Error<T> {
24+
/// MaxEntries limit reached.
25+
TooManyEntries,
26+
}
27+
28+
fn add_entry(entry: u32) -> Result<(), DispatchError> {
29+
// Adds a new entry by reading the whole storage item and editing it. This is inefficient.
30+
Entries::<T>::try_mutate(|entries| {
31+
entries.try_push(entry).map_err(|_| Error::<T>::TooManyEntries)?;
32+
Ok(())
33+
})
34+
}
35+
```
36+
37+
## Best practice
38+
39+
Use `try_append` for improved readability and efficiency.
40+
41+
```rust
42+
fn add_entry(entry: u32) -> Result<(), DispatchError> {
43+
// Adds a new entry efficiently.
44+
Entries::<T>::try_append(entry).map_err(|_| Error::<T>::TooManyEntries)?;
45+
Ok(())
46+
}
47+
```
48+

0 commit comments

Comments
 (0)