referrerpolicy=no-referrer-when-downgrade

frame_support/storage/
transactional.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Provides functionality around the transaction storage.
19//!
20//! Transactional storage provides functionality to run an entire code block
21//! in a storage transaction. This means that either the entire changes to the
22//! storage are committed or everything is thrown away. This simplifies the
23//! writing of functionality that may bail at any point of operation. Otherwise
24//! you would need to first verify all storage accesses and then do the storage
25//! modifications.
26//!
27//! [`with_transaction`] provides a way to run a given closure in a transactional context.
28
29use sp_io::storage::{commit_transaction, rollback_transaction, start_transaction};
30use sp_runtime::{DispatchError, TransactionOutcome, TransactionalError};
31
32/// The type that is being used to store the current number of active layers.
33pub type Layer = u32;
34/// The key that is holds the current number of active layers.
35///
36/// Encodes to `0x3a7472616e73616374696f6e5f6c6576656c3a`.
37pub const TRANSACTION_LEVEL_KEY: &[u8] = b":transaction_level:";
38/// The maximum number of nested layers.
39pub const TRANSACTIONAL_LIMIT: Layer = 255;
40
41/// Returns the current number of nested transactional layers.
42fn get_transaction_level() -> Layer {
43	crate::storage::unhashed::get_or_default::<Layer>(TRANSACTION_LEVEL_KEY)
44}
45
46/// Set the current number of nested transactional layers.
47fn set_transaction_level(level: Layer) {
48	crate::storage::unhashed::put::<Layer>(TRANSACTION_LEVEL_KEY, &level);
49}
50
51/// Kill the transactional layers storage.
52fn kill_transaction_level() {
53	crate::storage::unhashed::kill(TRANSACTION_LEVEL_KEY);
54}
55
56/// Increments the transaction level. Returns an error if levels go past the limit.
57///
58/// Returns a guard that when dropped decrements the transaction level automatically.
59fn inc_transaction_level() -> Result<StorageLayerGuard, ()> {
60	let existing_levels = get_transaction_level();
61	if existing_levels >= TRANSACTIONAL_LIMIT {
62		return Err(())
63	}
64	// Cannot overflow because of check above.
65	set_transaction_level(existing_levels + 1);
66	Ok(StorageLayerGuard)
67}
68
69fn dec_transaction_level() {
70	let existing_levels = get_transaction_level();
71	if existing_levels == 0 {
72		log::warn!(
73				"We are underflowing with calculating transactional levels. Not great, but let's not panic...",
74			);
75	} else if existing_levels == 1 {
76		// Don't leave any trace of this storage item.
77		kill_transaction_level();
78	} else {
79		// Cannot underflow because of checks above.
80		set_transaction_level(existing_levels - 1);
81	}
82}
83
84struct StorageLayerGuard;
85
86impl Drop for StorageLayerGuard {
87	fn drop(&mut self) {
88		dec_transaction_level()
89	}
90}
91
92/// Check if the current call is within a transactional layer.
93pub fn is_transactional() -> bool {
94	get_transaction_level() > 0
95}
96
97/// Execute the supplied function in a new storage transaction.
98///
99/// All changes to storage performed by the supplied function are discarded if the returned
100/// outcome is `TransactionOutcome::Rollback`.
101///
102/// Transactions can be nested up to `TRANSACTIONAL_LIMIT` times; more than that will result in an
103/// error.
104///
105/// Commits happen to the parent transaction.
106pub fn with_transaction<T, E, F>(f: F) -> Result<T, E>
107where
108	E: From<DispatchError>,
109	F: FnOnce() -> TransactionOutcome<Result<T, E>>,
110{
111	// This needs to happen before `start_transaction` below.
112	// Otherwise we may rollback the increase, then decrease as the guard goes out of scope
113	// and then end in some bad state.
114	let _guard = inc_transaction_level().map_err(|()| TransactionalError::LimitReached.into())?;
115
116	start_transaction();
117
118	match f() {
119		TransactionOutcome::Commit(res) => {
120			commit_transaction();
121			res
122		},
123		TransactionOutcome::Rollback(res) => {
124			rollback_transaction();
125			res
126		},
127	}
128}
129
130/// Same as [`with_transaction`] but casts any internal error to `()`.
131///
132/// This rids `E` of the `From<DispatchError>` bound that is required by `with_transaction`.
133pub fn with_transaction_opaque_err<T, E, F>(f: F) -> Result<Result<T, E>, ()>
134where
135	F: FnOnce() -> TransactionOutcome<Result<T, E>>,
136{
137	with_transaction(move || -> TransactionOutcome<Result<Result<T, E>, DispatchError>> {
138		match f() {
139			TransactionOutcome::Commit(res) => TransactionOutcome::Commit(Ok(res)),
140			TransactionOutcome::Rollback(res) => TransactionOutcome::Rollback(Ok(res)),
141		}
142	})
143	.map_err(|_| ())
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.
151pub fn with_transaction_unchecked<R, F>(f: F) -> R
152where
153	F: FnOnce() -> TransactionOutcome<R>,
154{
155	// This needs to happen before `start_transaction` below.
156	// Otherwise we may rollback the increase, then decrease as the guard goes out of scope
157	// and then end in some bad state.
158	let maybe_guard = inc_transaction_level();
159
160	if maybe_guard.is_err() {
161		log::warn!(
162			"The transactional layer limit has been reached, and new transactional layers are being
163			spawned with `with_transaction_unchecked`. This could be caused by someone trying to
164			attack your chain, and you should investigate usage of `with_transaction_unchecked` and
165			potentially migrate to `with_transaction`, which enforces a transactional limit.",
166		);
167	}
168
169	start_transaction();
170
171	match f() {
172		TransactionOutcome::Commit(res) => {
173			commit_transaction();
174			res
175		},
176		TransactionOutcome::Rollback(res) => {
177			rollback_transaction();
178			res
179		},
180	}
181}
182
183/// Execute the supplied function, adding a new storage layer.
184///
185/// This is the same as `with_transaction`, but assuming that any function returning an `Err` should
186/// rollback, and any function returning `Ok` should commit. This provides a cleaner API to the
187/// developer who wants this behavior.
188pub fn with_storage_layer<T, E, F>(f: F) -> Result<T, E>
189where
190	E: From<DispatchError>,
191	F: FnOnce() -> Result<T, E>,
192{
193	with_transaction(|| {
194		let r = f();
195		if r.is_ok() {
196			TransactionOutcome::Commit(r)
197		} else {
198			TransactionOutcome::Rollback(r)
199		}
200	})
201}
202
203/// Execute the supplied function, ensuring we are at least in one storage layer.
204///
205/// If we are already in a storage layer, we just execute the provided closure.
206/// If we are not, we execute the closure within a [`with_storage_layer`].
207pub fn in_storage_layer<T, E, F>(f: F) -> Result<T, E>
208where
209	E: From<DispatchError>,
210	F: FnOnce() -> Result<T, E>,
211{
212	if is_transactional() {
213		f()
214	} else {
215		with_storage_layer(f)
216	}
217}
218
219#[cfg(test)]
220mod tests {
221	use super::*;
222	use crate::{assert_noop, assert_ok};
223	use sp_io::TestExternalities;
224	use sp_runtime::DispatchResult;
225
226	#[test]
227	fn is_transactional_should_return_false() {
228		TestExternalities::default().execute_with(|| {
229			assert!(!is_transactional());
230		});
231	}
232
233	#[test]
234	fn is_transactional_should_not_error_in_with_transaction() {
235		TestExternalities::default().execute_with(|| {
236			assert_ok!(with_transaction(|| -> TransactionOutcome<DispatchResult> {
237				assert!(is_transactional());
238				TransactionOutcome::Commit(Ok(()))
239			}));
240
241			assert_noop!(
242				with_transaction(|| -> TransactionOutcome<DispatchResult> {
243					assert!(is_transactional());
244					TransactionOutcome::Rollback(Err("revert".into()))
245				}),
246				"revert"
247			);
248		});
249	}
250
251	fn recursive_transactional(num: u32) -> DispatchResult {
252		if num == 0 {
253			return Ok(())
254		}
255
256		with_transaction(|| -> TransactionOutcome<DispatchResult> {
257			let res = recursive_transactional(num - 1);
258			TransactionOutcome::Commit(res)
259		})
260	}
261
262	#[test]
263	fn transaction_limit_should_work() {
264		TestExternalities::default().execute_with(|| {
265			assert_eq!(get_transaction_level(), 0);
266
267			assert_ok!(with_transaction(|| -> TransactionOutcome<DispatchResult> {
268				assert_eq!(get_transaction_level(), 1);
269				TransactionOutcome::Commit(Ok(()))
270			}));
271
272			assert_ok!(with_transaction(|| -> TransactionOutcome<DispatchResult> {
273				assert_eq!(get_transaction_level(), 1);
274				let res = with_transaction(|| -> TransactionOutcome<DispatchResult> {
275					assert_eq!(get_transaction_level(), 2);
276					TransactionOutcome::Commit(Ok(()))
277				});
278				TransactionOutcome::Commit(res)
279			}));
280
281			assert_ok!(recursive_transactional(255));
282			assert_noop!(
283				recursive_transactional(256),
284				sp_runtime::TransactionalError::LimitReached
285			);
286
287			assert_eq!(get_transaction_level(), 0);
288		});
289	}
290
291	#[test]
292	fn in_storage_layer_works() {
293		TestExternalities::default().execute_with(|| {
294			assert_eq!(get_transaction_level(), 0);
295
296			let res = in_storage_layer(|| -> DispatchResult {
297				assert_eq!(get_transaction_level(), 1);
298				in_storage_layer(|| -> DispatchResult {
299					// We are still in the same layer :)
300					assert_eq!(get_transaction_level(), 1);
301					Ok(())
302				})
303			});
304
305			assert_ok!(res);
306
307			let res = in_storage_layer(|| -> DispatchResult {
308				assert_eq!(get_transaction_level(), 1);
309				in_storage_layer(|| -> DispatchResult {
310					// We are still in the same layer :)
311					assert_eq!(get_transaction_level(), 1);
312					Err("epic fail".into())
313				})
314			});
315
316			assert_noop!(res, "epic fail");
317		});
318	}
319}