Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ef52c7f
Adds `AllowanceBasedPriority` extension
bkchr Apr 22, 2026
a4d9ca9
Nit plus test
bkontur Apr 22, 2026
22362fb
Do not degrade user's performance in time - reset usage when refresh_…
bkontur Apr 23, 2026
5a2682e
Migration for Paseo
bkontur Apr 23, 2026
895ab04
Fix migration
bkontur Apr 23, 2026
38795c2
fmt
bkontur Apr 23, 2026
65ff285
Ping CI
bkontur Apr 23, 2026
820882e
Fix tests to match assignment semantics of `authorize`
bkontur Apr 27, 2026
398dc3a
Merge remote-tracking branch 'origin/main' into bkchr-priority-extension
bkontur Apr 27, 2026
16db0cc
Compilation for paseo after main merge
bkontur Apr 27, 2026
d78a9b1
Doc nit for refresh_authorization
bkontur Apr 27, 2026
bada2d7
Migration for paseo, which was merged meantime
bkontur Apr 27, 2026
7a3b9dc
Refactor allowance boost behind `BoostStrategy`, default to `FlatBoost`
bkontur Apr 28, 2026
0cc298b
Merge remote-tracking branch 'origin/main' into bkchr-priority-extension
bkontur Apr 28, 2026
4f0516e
Docs nits
bkontur Apr 28, 2026
955c0cc
Compute allowance boost against post-this-tx state
bkontur Apr 28, 2026
633fe32
Merge branch 'main' into bkchr-priority-extension
bkontur Apr 28, 2026
bbf39df
Minimalistic alignment with https://github.com/paritytech/individuali…
bkontur Apr 29, 2026
1d0f322
Pick transactions_used / transactions_allowance from #469
bkontur Apr 29, 2026
1de60a1
transactions_used -> transactions
bkontur Apr 29, 2026
aefb13c
Refresh: only extend expiry, do not reset consumed counters
bkontur Apr 29, 2026
428f79d
Move back to 14
bkontur Apr 29, 2026
c8d0f7c
Tighten priority constants for store / renew
bkontur Apr 29, 2026
ab29ac5
Nit
bkontur Apr 29, 2026
71e05c5
More nits
bkontur Apr 29, 2026
8556690
Nit
bkontur Apr 30, 2026
05c5893
nit
bkontur Apr 30, 2026
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
39 changes: 15 additions & 24 deletions pallets/transaction-storage/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,12 @@ mod benchmarks {
let origin = T::Authorizer::try_successful_origin()
.map_err(|_| BenchmarkError::Stop("unable to compute origin"))?;
let who: T::AccountId = whitelisted_caller();
let transactions = 10;
let bytes: u64 = 1024 * 1024;

#[extrinsic_call]
_(origin as T::RuntimeOrigin, who.clone(), transactions, bytes);
_(origin as T::RuntimeOrigin, who.clone(), bytes);

assert_last_event::<T>(Event::AccountAuthorized { who, transactions, bytes }.into());
assert_last_event::<T>(Event::AccountAuthorized { who, bytes }.into());
Comment thread
bkontur marked this conversation as resolved.
Outdated
Ok(())
}

Expand All @@ -215,16 +214,10 @@ mod benchmarks {
let origin = T::Authorizer::try_successful_origin()
.map_err(|_| BenchmarkError::Stop("unable to compute origin"))?;
let who: T::AccountId = whitelisted_caller();
let transactions = 10;
let bytes: u64 = 1024 * 1024;
let origin2 = origin.clone();
TransactionStorage::<T>::authorize_account(
origin2 as T::RuntimeOrigin,
who.clone(),
transactions,
bytes,
)
.map_err(|_| BenchmarkError::Stop("unable to authorize account"))?;
TransactionStorage::<T>::authorize_account(origin2 as T::RuntimeOrigin, who.clone(), bytes)
.map_err(|_| BenchmarkError::Stop("unable to authorize account"))?;

#[extrinsic_call]
_(origin as T::RuntimeOrigin, who.clone());
Expand Down Expand Up @@ -273,7 +266,7 @@ mod benchmarks {
let origin = T::Authorizer::try_successful_origin()
.map_err(|_| BenchmarkError::Stop("unable to compute origin"))?;
let who: T::AccountId = whitelisted_caller();
TransactionStorage::<T>::authorize_account(origin, who.clone(), 1, 1)
TransactionStorage::<T>::authorize_account(origin, who.clone(), 1)
.map_err(|_| BenchmarkError::Stop("unable to authorize account"))?;

let period = T::AuthorizationPeriod::get();
Expand Down Expand Up @@ -314,13 +307,11 @@ mod benchmarks {
.map_err(|_| BenchmarkError::Stop("unable to compute origin"))?;
let caller: T::AccountId = whitelisted_caller();
let data = vec![0u8; l as usize];
let transactions = 10;
let bytes = l as u64 * 10;
let bytes_allowance = l as u64 * 10;
TransactionStorage::<T>::authorize_account(
origin as T::RuntimeOrigin,
caller.clone(),
transactions,
bytes,
bytes_allowance,
)
.map_err(|_| BenchmarkError::Stop("unable to authorize account"))?;

Expand All @@ -340,9 +331,10 @@ mod benchmarks {
.unwrap();
}

// prepare consumed one transaction worth of authorization
// prepare added `l` bytes to the used counter
let extent = TransactionStorage::<T>::account_authorization_extent(caller);
assert_eq!(extent.transactions, transactions - 1);
assert_eq!(extent.bytes, l as u64);
assert_eq!(extent.bytes_allowance, bytes_allowance);
Ok(())
}

Expand All @@ -355,13 +347,11 @@ mod benchmarks {
let origin = T::Authorizer::try_successful_origin()
.map_err(|_| BenchmarkError::Stop("unable to compute origin"))?;
let caller: T::AccountId = whitelisted_caller();
let transactions = 10;
let bytes = T::MaxTransactionSize::get() as u64 * 10;
let bytes_allowance = T::MaxTransactionSize::get() as u64 * 10;
TransactionStorage::<T>::authorize_account(
origin as T::RuntimeOrigin,
caller.clone(),
transactions,
bytes,
bytes_allowance,
)
.map_err(|_| BenchmarkError::Stop("unable to authorize account"))?;

Expand All @@ -382,9 +372,10 @@ mod benchmarks {
.unwrap();
}

// prepare consumed one transaction worth of authorization
// prepare added `MaxTransactionSize` bytes to the used counter
let extent = TransactionStorage::<T>::account_authorization_extent(caller);
assert_eq!(extent.transactions, transactions - 1);
assert_eq!(extent.bytes, T::MaxTransactionSize::get() as u64);
assert_eq!(extent.bytes_allowance, bytes_allowance);
Ok(())
}

Expand Down
108 changes: 108 additions & 0 deletions pallets/transaction-storage/src/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,3 +272,111 @@ where
Ok(())
}
}

/// Maximum priority boost added to a signed `store` / `store_with_cid_config` transaction.
///
/// The actual boost scales linearly with the signer's unused allowance:
/// `BOOST * (bytes_allowance - bytes) / bytes_allowance`. Fresh grant yields the full boost;
/// at-cap or over-cap yields zero.
pub const ALLOWANCE_PRIORITY_BOOST: TransactionPriority = 1_000_000_000;

/// Computes `ALLOWANCE_PRIORITY_BOOST * (bytes_allowance - bytes) / bytes_allowance`.
///
/// Returns `0` when `bytes_allowance == 0` (missing / expired auth) or when `bytes >=
/// bytes_allowance` (over cap). Uses `u128` intermediate because
/// `ALLOWANCE_PRIORITY_BOOST (1e9) * u64_max_plausible_bytes (~1e12)` exceeds `u64::MAX`.
fn proportional_boost(bytes: u64, bytes_allowance: u64) -> TransactionPriority {
Comment thread
bkontur marked this conversation as resolved.
Outdated
if bytes_allowance == 0 {
return 0;
}
let remaining = bytes_allowance.saturating_sub(bytes);
((ALLOWANCE_PRIORITY_BOOST as u128 * remaining as u128) / bytes_allowance as u128) as u64
}

/// Boosts the priority of signed `store` / `store_with_cid_config` calls proportionally to
/// the signer's remaining byte allowance. Transactions over allowance still validate
/// (consumption happens in [`ValidateStorageCalls`]), they just don't get any boost.
#[derive(Clone, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, scale_info::TypeInfo)]
#[codec(encode_bound())]
#[codec(decode_bound())]
#[codec(mel_bound())]
#[scale_info(skip_type_params(T))]
pub struct AllowanceBasedPriority<T>(PhantomData<T>);

impl<T> Default for AllowanceBasedPriority<T> {
fn default() -> Self {
Self(PhantomData)
}
}

impl<T: Config + Send + Sync> fmt::Debug for AllowanceBasedPriority<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "AllowanceBasedPriority")
}

#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
Ok(())
}
}

impl<T: Config + Send + Sync> TransactionExtension<RuntimeCallOf<T>> for AllowanceBasedPriority<T>
where
RuntimeCallOf<T>: IsSubType<Call<T>>,
T::RuntimeOrigin: OriginTrait + AsSystemOriginSigner<T::AccountId>,
{
const IDENTIFIER: &'static str = "AllowanceBasedPriority";

type Implicit = ();
fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> {
Ok(())
}

type Val = ();
type Pre = ();

fn weight(&self, _call: &RuntimeCallOf<T>) -> Weight {
<T as frame_system::Config>::DbWeight::get().reads(1)
}

fn validate(
&self,
origin: T::RuntimeOrigin,
call: &RuntimeCallOf<T>,
_info: &DispatchInfoOf<RuntimeCallOf<T>>,
_len: usize,
_self_implicit: Self::Implicit,
_inherited_implication: &impl Implication,
_source: TransactionSource,
) -> ValidateResult<Self::Val, RuntimeCallOf<T>> {
let Some(who) = origin.as_system_origin_signer() else {
return Ok((ValidTransaction::default(), (), origin));
};

let Some(inner_call) = call.is_sub_type() else {
return Ok((ValidTransaction::default(), (), origin));
};

if !matches!(inner_call, Call::store { .. } | Call::store_with_cid_config { .. }) {
return Ok((ValidTransaction::default(), (), origin));
}

let extent = Pallet::<T>::account_authorization_extent(who.clone());
let priority = proportional_boost(extent.bytes, extent.bytes_allowance);
let valid = ValidTransaction { priority, ..Default::default() };

Ok((valid, (), origin))
}

fn prepare(
self,
_val: Self::Val,
_origin: &T::RuntimeOrigin,
_call: &RuntimeCallOf<T>,
_info: &DispatchInfoOf<RuntimeCallOf<T>>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Ok(())
}
}
Loading
Loading