Extensible transactions (and tips) (#3102)

* Make extrinsics extensible.

Also Remove old extrinsic types.

* Rest of mockup. Add tips.

* Fix some build issues

* Runtiem builds :)

* Substrate builds.

* Fix a doc test

* Compact encoding

* Extract out the era logic into an extension

* Weight Check signed extension. (#3115)

* Weight signed extension.

* Revert a bit + test for check era.

* Update Cargo.toml

* Update node/cli/src/factory_impl.rs

* Update node/executor/src/lib.rs

* Update node/executor/src/lib.rs

* Don't use len for weight - use data.

* Operational Transaction; second attempt (#3138)

* working poc added.

* some fixes.

* Update doc.

* Fix all tests + final logic.

* more refactoring.

* nits.

* System block limit in bytes.

* Silent the storage macro warnings.

* More logic more tests.

* Fix import.

* Refactor names.

* Fix build.

* Update srml/balances/src/lib.rs

* Final refactor.

* Bump transaction version

* Fix weight mult test.

* Fix more tests and improve doc.

* Bump.

* Make some tests work again.

* Fix subkey.

* Remove todos + bump.

* Ignore expensive test.

* Bump.
This commit is contained in:
Gavin Wood
2019-07-23 01:06:49 +08:00
committed by Kian Peymani
parent 4f5654b67d
commit 78bc5edc14
55 changed files with 1965 additions and 1646 deletions
+372 -10
View File
@@ -76,16 +76,19 @@ use serde::Serialize;
use rstd::prelude::*;
#[cfg(any(feature = "std", test))]
use rstd::map;
use primitives::weights::{Weight, WeightMultiplier};
use primitives::{generic, traits::{self, CheckEqual, SimpleArithmetic,
use rstd::marker::PhantomData;
use primitives::generic::{self, Era};
use primitives::weights::{Weight, DispatchInfo, DispatchClass, WeightMultiplier};
use primitives::transaction_validity::{ValidTransaction, TransactionPriority, TransactionLongevity};
use primitives::traits::{self, CheckEqual, SimpleArithmetic, Zero, SignedExtension, Convert,
SimpleBitOps, Hash, Member, MaybeDisplay, EnsureOrigin, CurrentHeight, BlockNumberToHash,
MaybeSerializeDebugButNotDeserialize, MaybeSerializeDebug, StaticLookup, One, Bounded, Lookup,
Zero, Convert,
}};
MaybeSerializeDebugButNotDeserialize, MaybeSerializeDebug, StaticLookup, One, Bounded,
Lookup, DispatchError, SaturatedConversion,
};
use substrate_primitives::storage::well_known_keys;
use srml_support::{
storage, decl_module, decl_event, decl_storage, StorageDoubleMap, StorageValue,
StorageMap, Parameter, for_each_tuple, traits::{Contains, Get},
storage, decl_module, decl_event, decl_storage, StorageDoubleMap, StorageValue, StorageMap,
Parameter, for_each_tuple, traits::{Contains, Get}
};
use safe_mix::TripletMix;
use parity_codec::{Encode, Decode};
@@ -194,6 +197,12 @@ pub trait Trait: 'static + Eq + Clone {
/// Maximum number of block number to block hash mappings to keep (oldest pruned first).
type BlockHashCount: Get<Self::BlockNumber>;
/// The maximum weight of a block.
type MaximumBlockWeight: Get<Weight>;
/// The maximum length of a block (in bytes).
type MaximumBlockLength: Get<u32>;
}
pub type DigestOf<T> = generic::Digest<<T as Trait>::Hash>;
@@ -325,6 +334,8 @@ decl_storage! {
ExtrinsicCount: Option<u32>;
/// Total weight for all extrinsics put together, for the current block.
AllExtrinsicsWeight: Option<Weight>;
/// Total length (in bytes) for all extrinsics put together, for the current block.
AllExtrinsicsLen: Option<u32>;
/// The next weight multiplier. This should be updated at the end of each block based on the
/// saturation level (weight).
pub NextWeightMultiplier get(next_weight_multiplier): WeightMultiplier = Default::default();
@@ -549,6 +560,10 @@ impl<T: Trait> Module<T> {
AllExtrinsicsWeight::get().unwrap_or_default()
}
pub fn all_extrinsics_len() -> u32 {
AllExtrinsicsLen::get().unwrap_or_default()
}
/// Update the next weight multiplier.
///
/// This should be called at then end of each block, before `all_extrinsics_weight` is cleared.
@@ -590,6 +605,7 @@ impl<T: Trait> Module<T> {
ExtrinsicCount::kill();
Self::update_weight_multiplier();
AllExtrinsicsWeight::kill();
AllExtrinsicsLen::kill();
let number = <Number<T>>::take();
let parent_hash = <ParentHash<T>>::take();
@@ -744,17 +760,15 @@ impl<T: Trait> Module<T> {
}
/// To be called immediately after an extrinsic has been applied.
pub fn note_applied_extrinsic(r: &Result<(), &'static str>, weight: Weight) {
pub fn note_applied_extrinsic(r: &Result<(), &'static str>, _encoded_len: u32) {
Self::deposit_event(match r {
Ok(_) => Event::ExtrinsicSuccess,
Err(_) => Event::ExtrinsicFailed,
}.into());
let next_extrinsic_index = Self::extrinsic_index().unwrap_or_default() + 1u32;
let total_weight = weight.saturating_add(Self::all_extrinsics_weight());
storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &next_extrinsic_index);
AllExtrinsicsWeight::put(&total_weight);
}
/// To be called immediately after `note_applied_extrinsic` of the last extrinsic of the block
@@ -772,6 +786,207 @@ impl<T: Trait> Module<T> {
}
}
/// resource limit check.
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
pub struct CheckWeight<T: Trait + Send + Sync>(PhantomData<T>);
impl<T: Trait + Send + Sync> CheckWeight<T> {
/// Get the quota divisor of each dispatch class type. This indicates that all operational
/// dispatches can use the full capacity of any resource, while user-triggered ones can consume
/// a quarter.
fn get_dispatch_limit_divisor(class: DispatchClass) -> Weight {
match class {
DispatchClass::Operational => 1,
DispatchClass::Normal => 4,
}
}
/// Checks if the current extrinsic can fit into the block with respect to block weight limits.
///
/// Upon successes, it returns the new block weight as a `Result`.
fn check_weight(info: DispatchInfo) -> Result<Weight, DispatchError> {
let current_weight = Module::<T>::all_extrinsics_weight();
let maximum_weight = T::MaximumBlockWeight::get();
let limit = maximum_weight / Self::get_dispatch_limit_divisor(info.class);
let added_weight = info.weight.min(limit);
let next_weight = current_weight.saturating_add(added_weight);
if next_weight > limit {
return Err(DispatchError::BadState)
}
Ok(next_weight)
}
/// Checks if the current extrinsic can fit into the block with respect to block length limits.
///
/// Upon successes, it returns the new block length as a `Result`.
fn check_block_length(info: DispatchInfo, len: usize) -> Result<u32, DispatchError> {
let current_len = Module::<T>::all_extrinsics_len();
let maximum_len = T::MaximumBlockLength::get();
let limit = maximum_len / Self::get_dispatch_limit_divisor(info.class);
let added_len = len as u32;
let next_len = current_len.saturating_add(added_len);
if next_len > limit {
return Err(DispatchError::BadState)
}
Ok(next_len)
}
/// get the priority of an extrinsic denoted by `info`.
fn get_priority(info: DispatchInfo) -> TransactionPriority {
match info.class {
DispatchClass::Normal => info.weight.into(),
DispatchClass::Operational => Bounded::max_value()
}
}
/// Utility constructor for tests and client code.
#[cfg(feature = "std")]
pub fn from() -> Self {
Self(PhantomData)
}
}
impl<T: Trait + Send + Sync> SignedExtension for CheckWeight<T> {
type AccountId = T::AccountId;
type AdditionalSigned = ();
fn additional_signed(&self) -> rstd::result::Result<(), &'static str> { Ok(()) }
fn pre_dispatch(
self,
_who: &Self::AccountId,
info: DispatchInfo,
len: usize,
) -> Result<(), DispatchError> {
let next_len = Self::check_block_length(info, len)?;
AllExtrinsicsLen::put(next_len);
let next_weight = Self::check_weight(info)?;
AllExtrinsicsWeight::put(next_weight);
Ok(())
}
fn validate(
&self,
_who: &Self::AccountId,
info: DispatchInfo,
len: usize,
) -> Result<ValidTransaction, DispatchError> {
// There is no point in writing to storage here since changes are discarded. This basically
// discards any transaction which is bigger than the length or weight limit alone, which is
// a guarantee that it will fail in the pre-dispatch phase.
let _ = Self::check_block_length(info, len)?;
let _ = Self::check_weight(info)?;
Ok(ValidTransaction { priority: Self::get_priority(info), ..Default::default() })
}
}
#[cfg(feature = "std")]
impl<T: Trait + Send + Sync> rstd::fmt::Debug for CheckWeight<T> {
fn fmt(&self, f: &mut rstd::fmt::Formatter) -> rstd::fmt::Result {
write!(f, "CheckWeight<T>")
}
}
/// Nonce check and increment to give replay protection for transactions.
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
pub struct CheckNonce<T: Trait>(#[codec(compact)] T::Index);
#[cfg(feature = "std")]
impl<T: Trait> CheckNonce<T> {
/// utility constructor. Used only in client/factory code.
pub fn from(nonce: T::Index) -> Self {
Self(nonce)
}
}
#[cfg(feature = "std")]
impl<T: Trait> rstd::fmt::Debug for CheckNonce<T> {
fn fmt(&self, f: &mut rstd::fmt::Formatter) -> rstd::fmt::Result {
self.0.fmt(f)
}
}
impl<T: Trait> SignedExtension for CheckNonce<T> {
type AccountId = T::AccountId;
type AdditionalSigned = ();
fn additional_signed(&self) -> rstd::result::Result<(), &'static str> { Ok(()) }
fn pre_dispatch(
self,
who: &Self::AccountId,
_info: DispatchInfo,
_len: usize,
) -> Result<(), DispatchError> {
let expected = <AccountNonce<T>>::get(who);
if self.0 != expected {
return Err(
if self.0 < expected { DispatchError::Stale } else { DispatchError::Future }
)
}
<AccountNonce<T>>::insert(who, expected + T::Index::one());
Ok(())
}
fn validate(
&self,
who: &Self::AccountId,
info: DispatchInfo,
_len: usize,
) -> Result<ValidTransaction, DispatchError> {
// check index
let expected = <AccountNonce<T>>::get(who);
if self.0 < expected {
return Err(DispatchError::Stale)
}
let provides = vec![Encode::encode(&(who, self.0))];
let requires = if expected < self.0 {
vec![Encode::encode(&(who, self.0 - One::one()))]
} else {
vec![]
};
Ok(ValidTransaction {
priority: info.weight as TransactionPriority,
requires,
provides,
longevity: TransactionLongevity::max_value(),
propagate: true,
})
}
}
/// Nonce check and increment to give replay protection for transactions.
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
pub struct CheckEra<T: Trait + Send + Sync>((Era, rstd::marker::PhantomData<T>));
#[cfg(feature = "std")]
impl<T: Trait + Send + Sync> CheckEra<T> {
/// utility constructor. Used only in client/factory code.
pub fn from(era: Era) -> Self {
Self((era, rstd::marker::PhantomData))
}
}
#[cfg(feature = "std")]
impl<T: Trait + Send + Sync> rstd::fmt::Debug for CheckEra<T> {
fn fmt(&self, f: &mut rstd::fmt::Formatter) -> rstd::fmt::Result {
self.0.fmt(f)
}
}
impl<T: Trait + Send + Sync> SignedExtension for CheckEra<T> {
type AccountId = T::AccountId;
type AdditionalSigned = T::Hash;
fn additional_signed(&self) -> Result<Self::AdditionalSigned, &'static str> {
let current_u64 = <Module<T>>::block_number().saturated_into::<u64>();
let n = (self.0).0.birth(current_u64).saturated_into::<T::BlockNumber>();
if !<BlockHash<T>>::exists(n) { Err("transaction birth block ancient")? }
Ok(<Module<T>>::block_hash(n))
}
}
pub struct ChainContext<T>(::rstd::marker::PhantomData<T>);
impl<T> Default for ChainContext<T> {
fn default() -> Self {
@@ -819,6 +1034,8 @@ mod tests {
parameter_types! {
pub const BlockHashCount: u64 = 10;
pub const MaximumBlockWeight: Weight = 1024;
pub const MaximumBlockLength: u32 = 2 * 1024;
}
impl Trait for Test {
@@ -833,6 +1050,8 @@ mod tests {
type WeightMultiplierUpdate = ();
type Event = u16;
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
type MaximumBlockLength = MaximumBlockLength;
}
impl From<Event> for u16 {
@@ -983,4 +1202,147 @@ mod tests {
}
})
}
#[test]
fn signed_ext_check_nonce_works() {
with_externalities(&mut new_test_ext(), || {
<AccountNonce<Test>>::insert(1, 1);
let info = DispatchInfo::default();
let len = 0_usize;
// stale
assert!(CheckNonce::<Test>(0).validate(&1, info, len).is_err());
assert!(CheckNonce::<Test>(0).pre_dispatch(&1, info, len).is_err());
// correct
assert!(CheckNonce::<Test>(1).validate(&1, info, len).is_ok());
assert!(CheckNonce::<Test>(1).pre_dispatch(&1, info, len).is_ok());
// future
assert!(CheckNonce::<Test>(5).validate(&1, info, len).is_ok());
assert!(CheckNonce::<Test>(5).pre_dispatch(&1, info, len).is_err());
})
}
#[test]
fn signed_ext_check_weight_works_user_tx() {
with_externalities(&mut new_test_ext(), || {
let small = DispatchInfo { weight: 100, ..Default::default() };
let medium = DispatchInfo {
weight: <MaximumBlockWeight as Get<Weight>>::get() / 4 - 1,
..Default::default()
};
let big = DispatchInfo {
weight: <MaximumBlockWeight as Get<Weight>>::get() / 4 + 1,
..Default::default()
};
let len = 0_usize;
let reset_check_weight = |i, f, s| {
AllExtrinsicsWeight::put(s);
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, i, len);
if f { assert!(r.is_err()) } else { assert!(r.is_ok()) }
};
reset_check_weight(small, false, 0);
reset_check_weight(medium, false, 0);
reset_check_weight(big, true, 1);
})
}
#[test]
fn signed_ext_check_weight_fee_works() {
with_externalities(&mut new_test_ext(), || {
let free = DispatchInfo { weight: 0, ..Default::default() };
let len = 0_usize;
assert_eq!(System::all_extrinsics_weight(), 0);
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, free, len);
assert!(r.is_ok());
assert_eq!(System::all_extrinsics_weight(), 0);
})
}
#[test]
fn signed_ext_check_weight_max_works() {
with_externalities(&mut new_test_ext(), || {
let max = DispatchInfo { weight: Weight::max_value(), ..Default::default() };
let len = 0_usize;
assert_eq!(System::all_extrinsics_weight(), 0);
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, max, len);
assert!(r.is_ok());
assert_eq!(System::all_extrinsics_weight(), <MaximumBlockWeight as Get<Weight>>::get() / 4);
})
}
#[test]
fn signed_ext_check_weight_works_operational_tx() {
with_externalities(&mut new_test_ext(), || {
let normal = DispatchInfo { weight: 100, ..Default::default() };
let op = DispatchInfo { weight: 100, class: DispatchClass::Operational };
let len = 0_usize;
// given almost full block
AllExtrinsicsWeight::put(<MaximumBlockWeight as Get<Weight>>::get() / 4);
// will not fit.
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, normal, len).is_err());
// will fit.
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, op, len).is_ok());
// likewise for length limit.
let len = 100_usize;
AllExtrinsicsLen::put(<MaximumBlockLength as Get<Weight>>::get() / 4);
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, normal, len).is_err());
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, op, len).is_ok());
})
}
#[test]
fn signed_ext_check_weight_priority_works() {
with_externalities(&mut new_test_ext(), || {
let normal = DispatchInfo { weight: 100, class: DispatchClass::Normal };
let op = DispatchInfo { weight: 100, class: DispatchClass::Operational };
let len = 0_usize;
assert_eq!(
CheckWeight::<Test>(PhantomData).validate(&1, normal, len).unwrap().priority,
100,
);
assert_eq!(
CheckWeight::<Test>(PhantomData).validate(&1, op, len).unwrap().priority,
Bounded::max_value(),
);
})
}
#[test]
fn signed_ext_check_weight_block_size_works() {
with_externalities(&mut new_test_ext(), || {
let tx = DispatchInfo::default();
let reset_check_weight = |s, f| {
AllExtrinsicsLen::put(0);
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, tx, s);
if f { assert!(r.is_err()) } else { assert!(r.is_ok()) }
};
reset_check_weight(128, false);
reset_check_weight(512, false);
reset_check_weight(513, true);
})
}
#[test]
fn signed_ext_check_era_should_work() {
with_externalities(&mut new_test_ext(), || {
// future
assert_eq!(
CheckEra::<Test>::from(Era::mortal(4, 2)).additional_signed().err().unwrap(),
"transaction birth block ancient"
);
// correct
System::set_block_number(13);
<BlockHash<Test>>::insert(12, H256::repeat_byte(1));
assert!(CheckEra::<Test>::from(Era::mortal(4, 12)).additional_signed().is_ok());
})
}
}