Skip to content

Commit e0d4114

Browse files
bkchrark0f
authored andcommitted
transactional: Fix some nitpicks (paritytech#11163)
* transactional: Fix some nitpicks This fixes some nitpicks related to the transactional storage stuff from me. As everything was merged too fast, here are some nitpicks from me. First, the entire functionality is moved into its own file to have a clear separation. Secondly I changed the `set_transactional_level` to not take `Layer` by reference. Besides that I have added some docs etc. * Add some comment * Move tests * 🤦
1 parent 7554ed1 commit e0d4114

File tree

3 files changed

+242
-204
lines changed

3 files changed

+242
-204
lines changed

frame/support/procedural/src/transactional.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub fn require_transactional(_attr: TokenStream, input: TokenStream) -> Result<T
4949
let output = quote! {
5050
#(#attrs)*
5151
#vis #sig {
52-
if !#crate_::storage::is_transactional() {
52+
if !#crate_::storage::transactional::is_transactional() {
5353
return Err(#crate_::sp_runtime::TransactionalError::NoLayer.into());
5454
}
5555
#block

frame/support/src/storage/mod.rs

+9-203
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
//! Stuff to do with the runtime's storage.
1919
20-
pub use self::types::StorageEntryMetadataBuilder;
2120
use crate::{
2221
hash::{ReversibleStorageHasher, StorageHasher},
2322
storage::types::{
@@ -27,12 +26,14 @@ use crate::{
2726
};
2827
use codec::{Decode, Encode, EncodeLike, FullCodec, FullEncode};
2928
use sp_core::storage::ChildInfo;
30-
pub use sp_runtime::TransactionOutcome;
31-
use sp_runtime::{
32-
generic::{Digest, DigestItem},
33-
DispatchError, TransactionalError,
34-
};
29+
use sp_runtime::generic::{Digest, DigestItem};
3530
use sp_std::prelude::*;
31+
32+
pub use self::{
33+
transactional::{with_transaction, with_transaction_unchecked},
34+
types::StorageEntryMetadataBuilder,
35+
};
36+
pub use sp_runtime::TransactionOutcome;
3637
pub use types::Key;
3738

3839
pub mod bounded_btree_map;
@@ -43,140 +44,11 @@ pub mod child;
4344
pub mod generator;
4445
pub mod hashed;
4546
pub mod migration;
47+
pub mod transactional;
4648
pub mod types;
4749
pub mod unhashed;
4850
pub mod weak_bounded_vec;
4951

50-
mod transaction_level_tracker {
51-
type Layer = u32;
52-
const TRANSACTION_LEVEL_KEY: &'static [u8] = b":transaction_level:";
53-
const TRANSACTIONAL_LIMIT: Layer = 255;
54-
55-
pub fn get_transaction_level() -> Layer {
56-
crate::storage::unhashed::get_or_default::<Layer>(TRANSACTION_LEVEL_KEY)
57-
}
58-
59-
fn set_transaction_level(level: &Layer) {
60-
crate::storage::unhashed::put::<Layer>(TRANSACTION_LEVEL_KEY, level);
61-
}
62-
63-
fn kill_transaction_level() {
64-
crate::storage::unhashed::kill(TRANSACTION_LEVEL_KEY);
65-
}
66-
67-
/// Increments the transaction level. Returns an error if levels go past the limit.
68-
///
69-
/// Returns a guard that when dropped decrements the transaction level automatically.
70-
pub fn inc_transaction_level() -> Result<StorageLayerGuard, ()> {
71-
let existing_levels = get_transaction_level();
72-
if existing_levels >= TRANSACTIONAL_LIMIT {
73-
return Err(())
74-
}
75-
// Cannot overflow because of check above.
76-
set_transaction_level(&(existing_levels + 1));
77-
Ok(StorageLayerGuard)
78-
}
79-
80-
fn dec_transaction_level() {
81-
let existing_levels = get_transaction_level();
82-
if existing_levels == 0 {
83-
log::warn!(
84-
"We are underflowing with calculating transactional levels. Not great, but let's not panic...",
85-
);
86-
} else if existing_levels == 1 {
87-
// Don't leave any trace of this storage item.
88-
kill_transaction_level();
89-
} else {
90-
// Cannot underflow because of checks above.
91-
set_transaction_level(&(existing_levels - 1));
92-
}
93-
}
94-
95-
pub fn is_transactional() -> bool {
96-
get_transaction_level() > 0
97-
}
98-
99-
pub struct StorageLayerGuard;
100-
101-
impl Drop for StorageLayerGuard {
102-
fn drop(&mut self) {
103-
dec_transaction_level()
104-
}
105-
}
106-
}
107-
108-
/// Check if the current call is within a transactional layer.
109-
pub fn is_transactional() -> bool {
110-
transaction_level_tracker::is_transactional()
111-
}
112-
113-
/// Execute the supplied function in a new storage transaction.
114-
///
115-
/// All changes to storage performed by the supplied function are discarded if the returned
116-
/// outcome is `TransactionOutcome::Rollback`.
117-
///
118-
/// Transactions can be nested up to `TRANSACTIONAL_LIMIT` times; more than that will result in an
119-
/// error.
120-
///
121-
/// Commits happen to the parent transaction.
122-
pub fn with_transaction<T, E>(f: impl FnOnce() -> TransactionOutcome<Result<T, E>>) -> Result<T, E>
123-
where
124-
E: From<DispatchError>,
125-
{
126-
use sp_io::storage::{commit_transaction, rollback_transaction, start_transaction};
127-
use TransactionOutcome::*;
128-
129-
let _guard = transaction_level_tracker::inc_transaction_level()
130-
.map_err(|()| TransactionalError::LimitReached.into())?;
131-
132-
start_transaction();
133-
134-
match f() {
135-
Commit(res) => {
136-
commit_transaction();
137-
res
138-
},
139-
Rollback(res) => {
140-
rollback_transaction();
141-
res
142-
},
143-
}
144-
}
145-
146-
/// Same as [`with_transaction`] but without a limit check on nested transactional layers.
147-
///
148-
/// This is mostly for backwards compatibility before there was a transactional layer limit.
149-
/// It is recommended to only use [`with_transaction`] to avoid users from generating too many
150-
/// transactional layers.
151-
pub fn with_transaction_unchecked<R>(f: impl FnOnce() -> TransactionOutcome<R>) -> R {
152-
use sp_io::storage::{commit_transaction, rollback_transaction, start_transaction};
153-
use TransactionOutcome::*;
154-
155-
let maybe_guard = transaction_level_tracker::inc_transaction_level();
156-
157-
if maybe_guard.is_err() {
158-
log::warn!(
159-
"The transactional layer limit has been reached, and new transactional layers are being
160-
spawned with `with_transaction_unchecked`. This could be caused by someone trying to
161-
attack your chain, and you should investigate usage of `with_transaction_unchecked` and
162-
potentially migrate to `with_transaction`, which enforces a transactional limit.",
163-
);
164-
}
165-
166-
start_transaction();
167-
168-
match f() {
169-
Commit(res) => {
170-
commit_transaction();
171-
res
172-
},
173-
Rollback(res) => {
174-
rollback_transaction();
175-
res
176-
},
177-
}
178-
}
179-
18052
/// A trait for working with macro-generated storage values under the substrate storage API.
18153
///
18254
/// Details on implementation can be found at [`generator::StorageValue`].
@@ -1473,13 +1345,12 @@ pub fn storage_prefix(pallet_name: &[u8], storage_name: &[u8]) -> [u8; 32] {
14731345
#[cfg(test)]
14741346
mod test {
14751347
use super::*;
1476-
use crate::{assert_noop, assert_ok, hash::Identity, Twox128};
1348+
use crate::{assert_ok, hash::Identity, Twox128};
14771349
use bounded_vec::BoundedVec;
14781350
use frame_support::traits::ConstU32;
14791351
use generator::StorageValue as _;
14801352
use sp_core::hashing::twox_128;
14811353
use sp_io::TestExternalities;
1482-
use sp_runtime::DispatchResult;
14831354
use weak_bounded_vec::WeakBoundedVec;
14841355

14851356
#[test]
@@ -1590,71 +1461,6 @@ mod test {
15901461
});
15911462
}
15921463

1593-
#[test]
1594-
fn is_transactional_should_return_false() {
1595-
TestExternalities::default().execute_with(|| {
1596-
assert!(!is_transactional());
1597-
});
1598-
}
1599-
1600-
#[test]
1601-
fn is_transactional_should_not_error_in_with_transaction() {
1602-
TestExternalities::default().execute_with(|| {
1603-
assert_ok!(with_transaction(|| -> TransactionOutcome<DispatchResult> {
1604-
assert!(is_transactional());
1605-
TransactionOutcome::Commit(Ok(()))
1606-
}));
1607-
1608-
assert_noop!(
1609-
with_transaction(|| -> TransactionOutcome<DispatchResult> {
1610-
assert!(is_transactional());
1611-
TransactionOutcome::Rollback(Err("revert".into()))
1612-
}),
1613-
"revert"
1614-
);
1615-
});
1616-
}
1617-
1618-
fn recursive_transactional(num: u32) -> DispatchResult {
1619-
if num == 0 {
1620-
return Ok(())
1621-
}
1622-
1623-
with_transaction(|| -> TransactionOutcome<DispatchResult> {
1624-
let res = recursive_transactional(num - 1);
1625-
TransactionOutcome::Commit(res)
1626-
})
1627-
}
1628-
1629-
#[test]
1630-
fn transaction_limit_should_work() {
1631-
TestExternalities::default().execute_with(|| {
1632-
assert_eq!(transaction_level_tracker::get_transaction_level(), 0);
1633-
1634-
assert_ok!(with_transaction(|| -> TransactionOutcome<DispatchResult> {
1635-
assert_eq!(transaction_level_tracker::get_transaction_level(), 1);
1636-
TransactionOutcome::Commit(Ok(()))
1637-
}));
1638-
1639-
assert_ok!(with_transaction(|| -> TransactionOutcome<DispatchResult> {
1640-
assert_eq!(transaction_level_tracker::get_transaction_level(), 1);
1641-
let res = with_transaction(|| -> TransactionOutcome<DispatchResult> {
1642-
assert_eq!(transaction_level_tracker::get_transaction_level(), 2);
1643-
TransactionOutcome::Commit(Ok(()))
1644-
});
1645-
TransactionOutcome::Commit(res)
1646-
}));
1647-
1648-
assert_ok!(recursive_transactional(255));
1649-
assert_noop!(
1650-
recursive_transactional(256),
1651-
sp_runtime::TransactionalError::LimitReached
1652-
);
1653-
1654-
assert_eq!(transaction_level_tracker::get_transaction_level(), 0);
1655-
});
1656-
}
1657-
16581464
#[test]
16591465
fn key_prefix_iterator_works() {
16601466
TestExternalities::default().execute_with(|| {

0 commit comments

Comments
 (0)