Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Partial fix for transaction priority #7034

Merged
merged 7 commits into from
Sep 9, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions frame/support/src/weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,30 @@ impl Default for DispatchClass {
}
}

/// Primitives related to priority management of Frame.
pub mod priority {
/// The starting point of all Operational transactions. 3/4 of u64::max_value().
pub const LIMIT: u64 = 13_835_058_055_282_163_711_u64;

/// Wrapper for priority of different dispatch classes.
///
/// This only makes sure that any value created for the operational dispatch class is
/// incremented by [`LIMIT`].
pub enum FrameTransactionPriority {
Normal(u64),
Operational(u64),
}

impl Into<u64> for FrameTransactionPriority {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be From<FrameTransactionPriority> for u64 which is more idiomatic (you get Into automatically).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I am not ashamed to say that I didn't know this was an exception to the rule that reads: in impl X for Y at least one of X or Y must be items in the local crate :D

fn into(self) -> u64 {
match self {
FrameTransactionPriority::Normal(inner) => inner,
FrameTransactionPriority::Operational(inner) => inner.saturating_add(LIMIT),
}
}
}
}

/// A bundle of static information collected from the `#[weight = $x]` attributes.
#[derive(Clone, Copy, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode)]
pub struct DispatchInfo {
Expand Down
6 changes: 3 additions & 3 deletions frame/system/src/extensions/check_nonce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use sp_runtime::{
traits::{SignedExtension, DispatchInfoOf, Dispatchable, One},
transaction_validity::{
ValidTransaction, TransactionValidityError, InvalidTransaction, TransactionValidity,
TransactionLongevity, TransactionPriority,
TransactionLongevity,
},
};
use sp_std::vec;
Expand Down Expand Up @@ -90,7 +90,7 @@ impl<T: Trait> SignedExtension for CheckNonce<T> where
&self,
who: &Self::AccountId,
_call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> TransactionValidity {
// check index
Expand All @@ -107,7 +107,7 @@ impl<T: Trait> SignedExtension for CheckNonce<T> where
};

Ok(ValidTransaction {
priority: info.weight as TransactionPriority,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't know what our take on SignedExtensions composability is, but we should make it clear that CheckWeight is now required, cause without it the prioritisation would not work at all.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it an issue if there is no prioritisation ?
I agree we should make clear that chain should handle their prioritisation correctly for production chain, but a dev chain can work without it no ?

Copy link
Contributor Author

@kianenigma kianenigma Sep 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh so this must have been the reason why we set the weight as priority in CheckWeight and CheckNonce.

I am ambivalent. Honestly, at the end of the day, I think all 3 of these signed extensions are absolutely vital for any chain: CheckNonce, CheckWeight, ChargeFee, and if someone is to faff around with them they should be super careful about the detail, so I am still in for not setting any priority here. I will also remove it from CheckWeight.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, no, CheckWeight is important because it is the only one that is bumping Operational class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I think this is correct because setting the distinction of Normal and Operational should be the concern of the system module, not TransactionPayment.

priority: 0,
requires,
provides,
longevity: TransactionLongevity::max_value(),
Expand Down
20 changes: 13 additions & 7 deletions frame/system/src/extensions/check_weight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use sp_runtime::{
};
use frame_support::{
traits::{Get},
weights::{PostDispatchInfo, DispatchInfo, DispatchClass},
weights::{PostDispatchInfo, DispatchInfo, DispatchClass, priority::FrameTransactionPriority},
StorageValue,
};

Expand Down Expand Up @@ -157,12 +157,18 @@ impl<T: Trait + Send + Sync> CheckWeight<T> where
}

/// get the priority of an extrinsic denoted by `info`.
///
/// Operational transaction will be given a fixed initial amount to be fairly distinguished from
/// the normal ones.
fn get_priority(info: &DispatchInfoOf<T::Call>) -> TransactionPriority {
match info.class {
DispatchClass::Normal => info.weight.into(),
// Don't use up the whole priority space, to allow things like `tip`
// to be taken into account as well.
DispatchClass::Operational => TransactionPriority::max_value() / 2,
// Normal transaction.
DispatchClass::Normal =>
FrameTransactionPriority::Normal(info.weight.into()).into(),
kianenigma marked this conversation as resolved.
Show resolved Hide resolved
// Don't use up the whole priority space, to allow things like `tip` to be taken into
// account as well.
DispatchClass::Operational =>
FrameTransactionPriority::Operational(info.weight.into()).into(),
kianenigma marked this conversation as resolved.
Show resolved Hide resolved
// Mandatory extrinsics are only for inherents; never transactions.
DispatchClass::Mandatory => TransactionPriority::min_value(),
}
Expand Down Expand Up @@ -496,7 +502,7 @@ mod tests {
}

#[test]
fn signed_ext() {
fn signed_ext_check_weight_works() {
new_test_ext().execute_with(|| {
let normal = DispatchInfo { weight: 100, class: DispatchClass::Normal, pays_fee: Pays::Yes };
let op = DispatchInfo { weight: 100, class: DispatchClass::Operational, pays_fee: Pays::Yes };
Expand All @@ -512,7 +518,7 @@ mod tests {
.validate(&1, CALL, &op, len)
.unwrap()
.priority;
assert_eq!(priority, u64::max_value() / 2);
assert_eq!(priority, frame_support::weights::priority::LIMIT + 100);
})
}

Expand Down
24 changes: 18 additions & 6 deletions frame/transaction-payment/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,20 @@ impl<T: Trait + Send + Sync> ChargeTransactionPayment<T> where
Err(_) => Err(InvalidTransaction::Payment.into()),
}
}

/// Get an appropriate priority for a transaction with the given length and info.
///
/// This will try and optimise the `fee/weight` `fee/length`, whichever is consuming more of the
/// maximum corresponding limit.
///
/// For example, if a transaction consumed 1/4th of the block length and half of the weight, its
/// final priority is `fee * 2`.
kianenigma marked this conversation as resolved.
Show resolved Hide resolved
fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {
let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);
let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);
let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();
final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if this operation would saturate too easily but it seems fine:

MaximumBlockWeight/BaseExtrinsicWeight ~ 10^4
MaximumBlockLength ~ 10^6
so coefficient is less than 10^6

TransactionByteFee ~ 10^10
WeightFee is at most 10^12 (MaximumBlockWeight being 10^12)
so final_fee is around 10^12 (if transaction len is not too big)

so this operation would result ~ 10^18 so it is less than 2^64 (== 10^19)

So it seems to handle all kind of transaction though not with big margin. I guess it seems fine

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah this could get tricky..

But yes I think your estimate is on the extreme end. A typical fee in a chain with 12 decimals (such as kusama) would probably be around 10^9~10^10. Given that a coefficient of even 10^6 is fine, even though that is also a very high estimate. This means that we have a transaction, which weight-wise or size-wise, is 10^6 times smaller than the block limit, i.e. a million of them can fit in a block. Do we have this in reality? I think most weights are such that we can fit a few thousand transactions in a block at most.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I agree.

My worry is if submitting a full block extrinsic saturate the priority then you can't tip beyond this. But tip is important for when you actually really need to get your transaction in.

}
}

impl<T: Trait + Send + Sync> sp_std::fmt::Debug for ChargeTransactionPayment<T> {
Expand Down Expand Up @@ -499,12 +513,10 @@ impl<T: Trait + Send + Sync> SignedExtension for ChargeTransactionPayment<T> whe
len: usize,
) -> TransactionValidity {
let (fee, _) = self.withdraw_fee(who, info, len)?;

let mut r = ValidTransaction::default();
// NOTE: we probably want to maximize the _fee (of any type) per weight unit_ here, which
// will be a bit more than setting the priority to tip. For now, this is enough.
r.priority = fee.saturated_into::<TransactionPriority>();
Ok(r)
Ok(ValidTransaction {
priority: Self::get_priority(len, info, fee),
..Default::default()
})
}

fn pre_dispatch(
Expand Down