Skip to content

Commit d7979d0

Browse files
authored
Implement 'transactional' annotation for runtime functions. (paritytech#6763)
* Implement 'transactional' annotation for runtime functions. * Allow function attributes for dispatchable calls in decl_module. * decl_module docs: add transactional function example. * decl_module docs: add function attributes notes. * Fix license header.
1 parent d4efdf0 commit d7979d0

File tree

5 files changed

+164
-7
lines changed

5 files changed

+164
-7
lines changed

frame/support/procedural/src/lib.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
mod storage;
2525
mod construct_runtime;
26+
mod transactional;
2627

2728
use proc_macro::TokenStream;
2829

@@ -289,3 +290,28 @@ pub fn decl_storage(input: TokenStream) -> TokenStream {
289290
pub fn construct_runtime(input: TokenStream) -> TokenStream {
290291
construct_runtime::construct_runtime(input)
291292
}
293+
294+
/// Execute the annotated function in a new storage transaction.
295+
///
296+
/// The return type of the annotated function must be `Result`. All changes to storage performed
297+
/// by the annotated function are discarded if it returns `Err`, or committed if `Ok`.
298+
///
299+
/// #Example
300+
///
301+
/// ```nocompile
302+
/// #[transactional]
303+
/// fn value_commits(v: u32) -> result::Result<u32, &'static str> {
304+
/// Value::set(v);
305+
/// Ok(v)
306+
/// }
307+
///
308+
/// #[transactional]
309+
/// fn value_rollbacks(v: u32) -> result::Result<u32, &'static str> {
310+
/// Value::set(v);
311+
/// Err("nah")
312+
/// }
313+
/// ```
314+
#[proc_macro_attribute]
315+
pub fn transactional(attr: TokenStream, input: TokenStream) -> TokenStream {
316+
transactional::transactional(attr, input)
317+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) 2020 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+
use proc_macro::TokenStream;
19+
use quote::quote;
20+
use syn::{parse_macro_input, ItemFn};
21+
22+
pub fn transactional(_attr: TokenStream, input: TokenStream) -> TokenStream {
23+
let ItemFn { attrs, vis, sig, block } = parse_macro_input!(input as ItemFn);
24+
25+
let output = quote! {
26+
#(#attrs)*
27+
#vis #sig {
28+
use frame_support::storage::{with_transaction, TransactionOutcome};
29+
with_transaction(|| {
30+
let r = #block;
31+
if r.is_ok() {
32+
TransactionOutcome::Commit(r)
33+
} else {
34+
TransactionOutcome::Rollback(r)
35+
}
36+
})
37+
}
38+
};
39+
output.into()
40+
}

frame/support/src/dispatch.rs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,28 @@ impl<T> Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug {}
167167
/// # fn main() {}
168168
/// ```
169169
///
170+
/// ### Transactional Function Example
171+
///
172+
/// Transactional function discards all changes to storage if it returns `Err`, or commits if
173+
/// `Ok`, via the #\[transactional\] attribute. Note the attribute must be after #\[weight\].
174+
///
175+
/// ```
176+
/// # #[macro_use]
177+
/// # extern crate frame_support;
178+
/// # use frame_support::transactional;
179+
/// # use frame_system::Trait;
180+
/// decl_module! {
181+
/// pub struct Module<T: Trait> for enum Call where origin: T::Origin {
182+
/// #[weight = 0]
183+
/// #[transactional]
184+
/// fn my_short_function(origin) {
185+
/// // Your implementation
186+
/// }
187+
/// }
188+
/// }
189+
/// # fn main() {}
190+
/// ```
191+
///
170192
/// ### Privileged Function Example
171193
///
172194
/// A privileged function checks that the origin of the call is `ROOT`.
@@ -189,6 +211,14 @@ impl<T> Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug {}
189211
/// # fn main() {}
190212
/// ```
191213
///
214+
/// ### Attributes on Functions
215+
///
216+
/// Attributes on functions are supported, but must be in the order of:
217+
/// 1. Optional #\[doc\] attribute.
218+
/// 2. #\[weight\] attribute.
219+
/// 3. Optional function attributes, for instance #\[transactional\]. Those function attributes will be written
220+
/// only on the dispatchable functions implemented on `Module`, not on the `Call` enum variant.
221+
///
192222
/// ## Multiple Module Instances Example
193223
///
194224
/// A Substrate module can be built such that multiple instances of the same module can be used within a single
@@ -1015,6 +1045,7 @@ macro_rules! decl_module {
10151045
[ $( $dispatchables:tt )* ]
10161046
$(#[doc = $doc_attr:tt])*
10171047
#[weight = $weight:expr]
1048+
$(#[$fn_attr:meta])*
10181049
$fn_vis:vis fn $fn_name:ident(
10191050
$origin:ident $( , $(#[$codec_attr:ident])* $param_name:ident : $param:ty )* $(,)?
10201051
) $( -> $result:ty )* { $( $impl:tt )* }
@@ -1039,6 +1070,7 @@ macro_rules! decl_module {
10391070
$( $dispatchables )*
10401071
$(#[doc = $doc_attr])*
10411072
#[weight = $weight]
1073+
$(#[$fn_attr])*
10421074
$fn_vis fn $fn_name(
10431075
$origin $( , $(#[$codec_attr])* $param_name : $param )*
10441076
) $( -> $result )* { $( $impl )* }
@@ -1066,6 +1098,7 @@ macro_rules! decl_module {
10661098
{ $( $integrity_test:tt )* }
10671099
[ $( $dispatchables:tt )* ]
10681100
$(#[doc = $doc_attr:tt])*
1101+
$(#[$fn_attr:meta])*
10691102
$fn_vis:vis fn $fn_name:ident(
10701103
$from:ident $( , $( #[$codec_attr:ident] )* $param_name:ident : $param:ty )* $(,)?
10711104
) $( -> $result:ty )* { $( $impl:tt )* }
@@ -1094,6 +1127,7 @@ macro_rules! decl_module {
10941127
[ $( $dispatchables:tt )* ]
10951128
$(#[doc = $doc_attr:tt])*
10961129
$(#[weight = $weight:expr])?
1130+
$(#[$fn_attr:meta])*
10971131
$fn_vis:vis fn $fn_name:ident(
10981132
$origin:ident : T::Origin $( , $( #[$codec_attr:ident] )* $param_name:ident : $param:ty )* $(,)?
10991133
) $( -> $result:ty )* { $( $impl:tt )* }
@@ -1121,6 +1155,7 @@ macro_rules! decl_module {
11211155
[ $( $dispatchables:tt )* ]
11221156
$(#[doc = $doc_attr:tt])*
11231157
$(#[weight = $weight:expr])?
1158+
$(#[$fn_attr:meta])*
11241159
$fn_vis:vis fn $fn_name:ident(
11251160
origin : $origin:ty $( , $( #[$codec_attr:ident] )* $param_name:ident : $param:ty )* $(,)?
11261161
) $( -> $result:ty )* { $( $impl:tt )* }
@@ -1148,6 +1183,7 @@ macro_rules! decl_module {
11481183
[ $( $dispatchables:tt )* ]
11491184
$(#[doc = $doc_attr:tt])*
11501185
$(#[weight = $weight:expr])?
1186+
$(#[$fn_attr:meta])*
11511187
$fn_vis:vis fn $fn_name:ident(
11521188
$( $(#[$codec_attr:ident])* $param_name:ident : $param:ty ),* $(,)?
11531189
) $( -> $result:ty )* { $( $impl:tt )* }
@@ -1410,13 +1446,13 @@ macro_rules! decl_module {
14101446
$origin_ty:ty;
14111447
$error_type:ty;
14121448
$ignore:ident;
1413-
$(#[doc = $doc_attr:tt])*
1449+
$(#[$fn_attr:meta])*
14141450
$vis:vis fn $name:ident (
14151451
$origin:ident $(, $param:ident : $param_ty:ty )*
14161452
) { $( $impl:tt )* }
14171453
) => {
1418-
$(#[doc = $doc_attr])*
14191454
#[allow(unreachable_code)]
1455+
$(#[$fn_attr])*
14201456
$vis fn $name(
14211457
$origin: $origin_ty $(, $param: $param_ty )*
14221458
) -> $crate::dispatch::DispatchResult {
@@ -1432,12 +1468,12 @@ macro_rules! decl_module {
14321468
$origin_ty:ty;
14331469
$error_type:ty;
14341470
$ignore:ident;
1435-
$(#[doc = $doc_attr:tt])*
1471+
$(#[$fn_attr:meta])*
14361472
$vis:vis fn $name:ident (
14371473
$origin:ident $(, $param:ident : $param_ty:ty )*
14381474
) -> $result:ty { $( $impl:tt )* }
14391475
) => {
1440-
$(#[doc = $doc_attr])*
1476+
$(#[$fn_attr])*
14411477
$vis fn $name($origin: $origin_ty $(, $param: $param_ty )* ) -> $result {
14421478
$crate::sp_tracing::enter_span!(stringify!($name));
14431479
$( $impl )*
@@ -1569,6 +1605,7 @@ macro_rules! decl_module {
15691605
$(
15701606
$(#[doc = $doc_attr:tt])*
15711607
#[weight = $weight:expr]
1608+
$(#[$fn_attr:meta])*
15721609
$fn_vis:vis fn $fn_name:ident(
15731610
$from:ident $( , $(#[$codec_attr:ident])* $param_name:ident : $param:ty)*
15741611
) $( -> $result:ty )* { $( $impl:tt )* }
@@ -1654,6 +1691,7 @@ macro_rules! decl_module {
16541691
$(#[doc = $doc_attr])*
16551692
///
16561693
/// NOTE: Calling this function will bypass origin filters.
1694+
$(#[$fn_attr])*
16571695
$fn_vis fn $fn_name (
16581696
$from $(, $param_name : $param )*
16591697
) $( -> $result )* { $( $impl )* }

frame/support/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ macro_rules! ord_parameter_types {
267267
}
268268

269269
#[doc(inline)]
270-
pub use frame_support_procedural::{decl_storage, construct_runtime};
270+
pub use frame_support_procedural::{decl_storage, construct_runtime, transactional};
271271

272272
/// Return Err of the expression: `return Err($expression);`.
273273
///

frame/support/test/tests/storage_transaction.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,32 @@
1717

1818
use codec::{Encode, Decode, EncodeLike};
1919
use frame_support::{
20-
StorageMap, StorageValue, storage::{with_transaction, TransactionOutcome::*},
20+
assert_ok, assert_noop, dispatch::{DispatchError, DispatchResult}, transactional, StorageMap, StorageValue,
21+
storage::{with_transaction, TransactionOutcome::*},
2122
};
2223
use sp_io::TestExternalities;
24+
use sp_std::result;
2325

2426
pub trait Trait {
2527
type Origin;
2628
type BlockNumber: Encode + Decode + EncodeLike + Default + Clone;
2729
}
2830

2931
frame_support::decl_module! {
30-
pub struct Module<T: Trait> for enum Call where origin: T::Origin {}
32+
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
33+
#[weight = 0]
34+
#[transactional]
35+
fn value_commits(_origin, v: u32) {
36+
Value::set(v);
37+
}
38+
39+
#[weight = 0]
40+
#[transactional]
41+
fn value_rollbacks(_origin, v: u32) -> DispatchResult {
42+
Value::set(v);
43+
Err(DispatchError::Other("nah"))
44+
}
45+
}
3146
}
3247

3348
frame_support::decl_storage!{
@@ -37,6 +52,11 @@ frame_support::decl_storage!{
3752
}
3853
}
3954

55+
struct Runtime;
56+
impl Trait for Runtime {
57+
type Origin = u32;
58+
type BlockNumber = u32;
59+
}
4060

4161
#[test]
4262
fn storage_transaction_basic_commit() {
@@ -157,3 +177,36 @@ fn storage_transaction_commit_then_rollback() {
157177
assert_eq!(Map::get("val3"), 0);
158178
});
159179
}
180+
181+
#[test]
182+
fn transactional_annotation() {
183+
#[transactional]
184+
fn value_commits(v: u32) -> result::Result<u32, &'static str> {
185+
Value::set(v);
186+
Ok(v)
187+
}
188+
189+
#[transactional]
190+
fn value_rollbacks(v: u32) -> result::Result<u32, &'static str> {
191+
Value::set(v);
192+
Err("nah")
193+
}
194+
195+
TestExternalities::default().execute_with(|| {
196+
assert_ok!(value_commits(2), 2);
197+
assert_eq!(Value::get(), 2);
198+
199+
assert_noop!(value_rollbacks(3), "nah");
200+
});
201+
}
202+
203+
#[test]
204+
fn transactional_annotation_in_decl_module() {
205+
TestExternalities::default().execute_with(|| {
206+
let origin = 0;
207+
assert_ok!(<Module<Runtime>>::value_commits(origin, 2));
208+
assert_eq!(Value::get(), 2);
209+
210+
assert_noop!(<Module<Runtime>>::value_rollbacks(origin, 3), "nah");
211+
});
212+
}

0 commit comments

Comments
 (0)