-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Make TransactionExtension tuple of tuple transparent for implication
#7028
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
d553973
2147529
080a19d
807e7f7
9d35e85
1892c8f
f77dbd4
59c62d4
8d4be95
36edc77
a84c613
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| title: 'Fix implication order in implementation of `TransactionExtension` for tuple' | ||
| doc: | ||
| - audience: | ||
| - Runtime Dev | ||
| - Runtime User | ||
| description: |- | ||
| Before this PR, the implications were different in the pipeline `(A, B, C)` and `((A, B), C)`. | ||
| This PR fixes this behavior and make nested tuple transparant, the implication order of tuple of | ||
| tuple is now the same as in a single tuple. | ||
|
|
||
| For runtime users this mean that the implication can be breaking depending on the pipeline used | ||
| in the runtime. | ||
|
|
||
| For runtime developers this breaks usage of `TransactionExtension::validate`. | ||
| When calling `TransactionExtension::validate` the implication must now implement `Implication` | ||
| trait, you can use `TxBaseImplication` to wrap the type and use it as the base implication. | ||
| E.g. instead of `&(extension_version, call),` you can write `&TxBaseImplication((extension_version, call))`. | ||
|
|
||
| crates: | ||
| - name: sp-runtime | ||
| bump: major | ||
| - name: pallet-skip-feeless-payment | ||
| bump: major | ||
| - name: frame-system | ||
| bump: major |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,59 @@ mod dispatch_transaction; | |
| pub use as_transaction_extension::AsTransactionExtension; | ||
| pub use dispatch_transaction::DispatchTransaction; | ||
|
|
||
| /// The base implication in a transaction. | ||
| /// | ||
| /// This struct is used to represent the base implication in the transaction, that is | ||
| /// the implication not part of any transaction extensions. It usually comprises of the call and | ||
| /// the transaction extension version. | ||
| /// | ||
| /// The concept of implication in the transaction extension pipeline is explained in the trait | ||
| /// documentation: [`TransactionExtension`]. | ||
| #[derive(Encode)] | ||
| pub struct TxBaseImplication<T>(pub T); | ||
|
|
||
| impl<T: Encode> Implication for TxBaseImplication<T> { | ||
| fn parts(&self) -> ImplicationParts<&impl Encode, &impl Encode, &impl Encode> { | ||
| ImplicationParts { base: self, explicit: &(), implicit: &() } | ||
| } | ||
| } | ||
|
|
||
| /// The implication in a transaction. | ||
| /// | ||
| /// The concept of implication in the transaction extension pipeline is explained in the trait | ||
| /// documentation: [`TransactionExtension`]. | ||
| #[derive(Encode)] | ||
| pub struct ImplicationParts<Base, Explicit, Implicit> { | ||
| /// The base implication, that is implication not part of any transaction extension, usually | ||
| /// the call and the transaction extension version, | ||
| pub base: Base, | ||
| /// The explicit implication in transaction extensions, | ||
gui1117 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| pub explicit: Explicit, | ||
| /// The implicit implication in transaction extensions. | ||
| pub implicit: Implicit, | ||
| } | ||
|
|
||
| impl<Base: Encode, Explicit: Encode, Implicit: Encode> Implication | ||
| for ImplicationParts<Base, Explicit, Implicit> | ||
| { | ||
| fn parts(&self) -> ImplicationParts<&impl Encode, &impl Encode, &impl Encode> { | ||
| ImplicationParts { base: &self.base, explicit: &self.explicit, implicit: &self.implicit } | ||
| } | ||
| } | ||
|
|
||
| /// Interface of implications in the transaction extension pipeline. | ||
| /// | ||
| /// Implications can be encoded, this is useful for checking signature on the implications. | ||
| /// Implications can be split into parts, this allow to destructure and restructure the | ||
| /// implications, this is useful for nested pipeline. | ||
| /// | ||
| /// The concept of implication in the transaction extension pipeline is explained in the trait | ||
| /// documentation: [`TransactionExtension`]. | ||
| pub trait Implication: Encode { | ||
gui1117 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// Destructure the implication into its parts. | ||
| fn parts(&self) -> ImplicationParts<&impl Encode, &impl Encode, &impl Encode>; | ||
| } | ||
|
|
||
| /// Shortcut for the result value of the `validate` function. | ||
| pub type ValidateResult<Val, Call> = | ||
| Result<(ValidTransaction, Val, DispatchOriginOf<Call>), TransactionValidityError>; | ||
|
|
@@ -244,7 +297,7 @@ pub trait TransactionExtension<Call: Dispatchable>: | |
| info: &DispatchInfoOf<Call>, | ||
| len: usize, | ||
| self_implicit: Self::Implicit, | ||
| inherited_implication: &impl Encode, | ||
| inherited_implication: &impl Implication, | ||
| source: TransactionSource, | ||
| ) -> ValidateResult<Self::Val, Call>; | ||
|
|
||
|
|
@@ -499,7 +552,7 @@ impl<Call: Dispatchable> TransactionExtension<Call> for Tuple { | |
| info: &DispatchInfoOf<Call>, | ||
| len: usize, | ||
| self_implicit: Self::Implicit, | ||
| inherited_implication: &impl Encode, | ||
| inherited_implication: &impl Implication, | ||
| source: TransactionSource, | ||
| ) -> Result< | ||
| (ValidTransaction, Self::Val, <Call as Dispatchable>::RuntimeOrigin), | ||
|
|
@@ -510,23 +563,20 @@ impl<Call: Dispatchable> TransactionExtension<Call> for Tuple { | |
| let following_explicit_implications = for_tuples!( ( #( &self.Tuple ),* ) ); | ||
| let following_implicit_implications = self_implicit; | ||
|
|
||
| let implication_parts = inherited_implication.parts(); | ||
|
|
||
| for_tuples!(#( | ||
| // Implication of this pipeline element not relevant for later items, so we pop it. | ||
| let (_item, following_explicit_implications) = following_explicit_implications.pop_front(); | ||
| let (item_implicit, following_implicit_implications) = following_implicit_implications.pop_front(); | ||
| let (item_valid, item_val, origin) = { | ||
| let implications = ( | ||
| // The first is the implications born of the fact we return the mutated | ||
| // origin. | ||
| inherited_implication, | ||
| // This is the explicitly made implication born of the fact the new origin is | ||
| // passed into the next items in this pipeline-tuple. | ||
| &following_explicit_implications, | ||
| // This is the implicitly made implication born of the fact the new origin is | ||
| // passed into the next items in this pipeline-tuple. | ||
| &following_implicit_implications, | ||
| ); | ||
| Tuple.validate(origin, call, info, len, item_implicit, &implications, source)? | ||
| Tuple.validate(origin, call, info, len, item_implicit, | ||
| &ImplicationParts { | ||
| base: implication_parts.base, | ||
| explicit: (&following_explicit_implications, implication_parts.explicit), | ||
| implicit: (&following_implicit_implications, implication_parts.implicit), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This keeps the order correct as
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On master the order is:
With this PR the order is in both case |
||
| }, | ||
| source)? | ||
| }; | ||
| let valid = valid.combine_with(item_valid); | ||
| let val = val.push_back(item_val); | ||
|
|
@@ -620,7 +670,7 @@ impl<Call: Dispatchable> TransactionExtension<Call> for () { | |
| _info: &DispatchInfoOf<Call>, | ||
| _len: usize, | ||
| _self_implicit: Self::Implicit, | ||
| _inherited_implication: &impl Encode, | ||
| _inherited_implication: &impl Implication, | ||
| _source: TransactionSource, | ||
| ) -> Result< | ||
| (ValidTransaction, (), <Call as Dispatchable>::RuntimeOrigin), | ||
|
|
@@ -639,3 +689,168 @@ impl<Call: Dispatchable> TransactionExtension<Call> for () { | |
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod test { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_implications_on_nested_structure() { | ||
| use scale_info::TypeInfo; | ||
| use std::cell::RefCell; | ||
|
|
||
| #[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, TypeInfo)] | ||
| struct MockExtension { | ||
| also_implicit: u8, | ||
| explicit: u8, | ||
| } | ||
|
|
||
| const CALL_IMPLICIT: u8 = 23; | ||
|
|
||
| thread_local! { | ||
| static COUNTER: RefCell<u8> = RefCell::new(1); | ||
| } | ||
|
|
||
| impl TransactionExtension<()> for MockExtension { | ||
| const IDENTIFIER: &'static str = "MockExtension"; | ||
| type Implicit = u8; | ||
| fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> { | ||
| Ok(self.also_implicit) | ||
| } | ||
| type Val = (); | ||
| type Pre = (); | ||
| fn weight(&self, _call: &()) -> Weight { | ||
| Weight::zero() | ||
| } | ||
| fn prepare( | ||
| self, | ||
| _val: Self::Val, | ||
| _origin: &DispatchOriginOf<()>, | ||
| _call: &(), | ||
| _info: &DispatchInfoOf<()>, | ||
| _len: usize, | ||
| ) -> Result<Self::Pre, TransactionValidityError> { | ||
| Ok(()) | ||
| } | ||
| fn validate( | ||
| &self, | ||
| origin: DispatchOriginOf<()>, | ||
| _call: &(), | ||
| _info: &DispatchInfoOf<()>, | ||
| _len: usize, | ||
| self_implicit: Self::Implicit, | ||
| inherited_implication: &impl Implication, | ||
| _source: TransactionSource, | ||
| ) -> ValidateResult<Self::Val, ()> { | ||
| COUNTER.with(|c| { | ||
| let mut counter = c.borrow_mut(); | ||
|
|
||
| assert_eq!(self_implicit, *counter); | ||
| assert_eq!( | ||
| self, | ||
| &MockExtension { also_implicit: *counter, explicit: *counter + 1 } | ||
| ); | ||
|
|
||
| // Implications must be call then 1 to 22 then 1 to 22 odd. | ||
| let mut assert_implications = Vec::new(); | ||
| assert_implications.push(CALL_IMPLICIT); | ||
| for i in *counter + 2..23 { | ||
| assert_implications.push(i); | ||
| } | ||
| for i in *counter + 2..23 { | ||
| if i % 2 == 1 { | ||
| assert_implications.push(i); | ||
| } | ||
| } | ||
| assert_eq!(inherited_implication.encode(), assert_implications,); | ||
gui1117 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| *counter += 2; | ||
| }); | ||
| Ok((ValidTransaction::default(), (), origin)) | ||
| } | ||
| fn post_dispatch_details( | ||
| _pre: Self::Pre, | ||
| _info: &DispatchInfoOf<()>, | ||
| _post_info: &PostDispatchInfoOf<()>, | ||
| _len: usize, | ||
| _result: &DispatchResult, | ||
| ) -> Result<Weight, TransactionValidityError> { | ||
| Ok(Weight::zero()) | ||
| } | ||
| } | ||
|
|
||
| // Test for one nested structure | ||
|
|
||
| let ext = ( | ||
| MockExtension { also_implicit: 1, explicit: 2 }, | ||
| MockExtension { also_implicit: 3, explicit: 4 }, | ||
| ( | ||
| MockExtension { also_implicit: 5, explicit: 6 }, | ||
| MockExtension { also_implicit: 7, explicit: 8 }, | ||
| ( | ||
| MockExtension { also_implicit: 9, explicit: 10 }, | ||
| MockExtension { also_implicit: 11, explicit: 12 }, | ||
| ), | ||
| MockExtension { also_implicit: 13, explicit: 14 }, | ||
| MockExtension { also_implicit: 15, explicit: 16 }, | ||
| ), | ||
| MockExtension { also_implicit: 17, explicit: 18 }, | ||
| (MockExtension { also_implicit: 19, explicit: 20 },), | ||
| MockExtension { also_implicit: 21, explicit: 22 }, | ||
| ); | ||
|
|
||
| let implicit = ext.implicit().unwrap(); | ||
|
|
||
| let res = ext | ||
| .validate( | ||
| (), | ||
| &(), | ||
| &DispatchInfoOf::<()>::default(), | ||
| 0, | ||
| implicit, | ||
| &TxBaseImplication(CALL_IMPLICIT), | ||
| TransactionSource::Local, | ||
| ) | ||
| .expect("valid"); | ||
|
|
||
| assert_eq!(res.0, ValidTransaction::default()); | ||
|
|
||
| // Test for another nested structure | ||
|
|
||
| COUNTER.with(|c| { | ||
| *c.borrow_mut() = 1; | ||
| }); | ||
|
|
||
| let ext = ( | ||
| MockExtension { also_implicit: 1, explicit: 2 }, | ||
| MockExtension { also_implicit: 3, explicit: 4 }, | ||
| MockExtension { also_implicit: 5, explicit: 6 }, | ||
| MockExtension { also_implicit: 7, explicit: 8 }, | ||
| MockExtension { also_implicit: 9, explicit: 10 }, | ||
| MockExtension { also_implicit: 11, explicit: 12 }, | ||
| ( | ||
| MockExtension { also_implicit: 13, explicit: 14 }, | ||
| MockExtension { also_implicit: 15, explicit: 16 }, | ||
| MockExtension { also_implicit: 17, explicit: 18 }, | ||
| MockExtension { also_implicit: 19, explicit: 20 }, | ||
| MockExtension { also_implicit: 21, explicit: 22 }, | ||
| ), | ||
| ); | ||
|
|
||
| let implicit = ext.implicit().unwrap(); | ||
|
|
||
| let res = ext | ||
| .validate( | ||
| (), | ||
| &(), | ||
| &DispatchInfoOf::<()>::default(), | ||
| 0, | ||
| implicit, | ||
| &TxBaseImplication(CALL_IMPLICIT), | ||
| TransactionSource::Local, | ||
| ) | ||
| .expect("valid"); | ||
|
|
||
| assert_eq!(res.0, ValidTransaction::default()); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.