Skip to content

Commit e543696

Browse files
bkchrbkonturfranciscoaguirre
authored
Adds AllowanceBasedPriority extension (#448)
* Adds `AllowanceBasedPriority` extension This extension gives `store` transactions a proportional priority boost based on the already consumed allowance. * Nit plus test * Do not degrade user's performance in time - reset usage when refresh_authorization * Migration for Paseo * Fix migration * fmt * Ping CI * Fix tests to match assignment semantics of `authorize` Re-authorizing an unexpired account/preimage replaces `bytes_allowance` rather than adding to it. Three tests assumed the additive form. Renamed `authorize_account_does_not_push_expiry` to `re_authorize_account_replaces_allowance_and_keeps_expiry` to reflect both invariants it now covers. * Compilation for paseo after main merge * Doc nit for refresh_authorization * Migration for paseo, which was merged meantime * Refactor allowance boost behind `BoostStrategy`, default to `FlatBoost` `AllowanceBasedPriority` is now generic over a `BoostStrategy` so the runtime can pick its policy in the `TxExtension` tuple. Two impls ship: - `ProportionalBoost`: original PR #448 behaviour — linear in remaining allowance. - `FlatBoost`: constant boost while in-budget, `0` once over-budget. The proportional form is vulnerable to censorship: a fresh-allowance signer outranks partly-used ones and can starve them with small-tx spam (see karolk91's analysis on PR #448). `FlatBoost` makes in-budget signers all rank equal and over-budget ones strictly lower; pool nonce/ arrival ordering breaks ties. Both runtimes are wired to `FlatBoost`. Three pallet unit tests (`proportional_scales_with_remaining_allowance`, `flat_is_constant_while_in_budget`, `flat_does_not_let_fresh_outrank_partly_used`) document the contracts and the censorship-relevant difference. * Docs nits * Compute allowance boost against post-this-tx state * Minimalistic alignment with paritytech/individuality#785 (part 1) * Pick transactions_used / transactions_allowance from #469 Brings just the AuthorizationExtent + boost-strategy parts of #469; the period / two-slot / bytes_permanent surface is left out. - AuthorizationExtent: gain transactions_used / transactions_allowance fields. authorize_account now uses its 3rd parameter to set/extend transactions_allowance; check_authorization saturating-bumps transactions_used on consume. - BoostStrategy: a shared in_budget helper gates both the byte and tx axes; FlatBoost is in-budget at-cap (matching #469); ProportionalBoost scales by the tighter of the two remainders. - AllowanceBasedPriority::validate increments transactions_used in the post-this-tx extent before computing the boost. - v1->v2 migration carries the old transaction-count field over to the new transactions_allowance. - Genesis config tuple becomes (account, transactions_allowance, bytes_allowance); preimage authorize_* always sets transactions_allowance = 1. - Tests + bulletin-{westend,paseo} runtime tests updated for the new expected counters; new boost_tests::tx_axis_gates_boost_independently. Out of scope (per #469): bytes_permanent, the two-slot current/next grant model, for_period parameter, renew matching in the boost extension, the People-Chain-aligned period model. Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com> * transactions_used -> transactions * Refresh: only extend expiry, do not reset consumed counters Per review feedback (#448 r3163274071): resetting `bytes` and `transactions` on refresh implicitly grants a fresh boost-tier window, which conflates two operations under one extrinsic. Refresh now does exactly what its name says — extend the expiration block. Holders who want more capacity call `authorize_account` (additive on the unexpired path). Doc updates on `refresh_account_authorization`, `refresh_preimage_authorization` and the `refresh_authorization` helper make the split explicit. `authorize_account`'s doc also gets updated to describe the new tx-axis additivity. * Move back to 14 * Tighten priority constants for store / renew - `AuthorizationPeriod` 90 days -> 14 days, aligning with the `LongTermStoragePeriodDuration` (2 weeks) on the People-Chain side. - Drop the unused `SudoPriority` and `SetPurgeKeysPriority` constants; they were only chained into `RemoveExpiredAuthorizationPriority` and not wired anywhere else. - `RemoveExpiredAuthorizationPriority` is now `TransactionPriority::MAX` so permissionless cleanups always outrank stores. - `StoreRenewPriority` is `TransactionPriority::MAX / 4`. With `AllowanceBasedPriority` adding `ALLOWANCE_PRIORITY_BOOST` for in-budget signers, in-budget txs land just above over-budget ones without saturating `u64` and with plenty of headroom both above generic transactions and below the cleanup ceiling. * Nit * More nits --------- Co-authored-by: Branislav Kontur <bkontur@gmail.com> Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com>
1 parent 0e32f0d commit e543696

19 files changed

Lines changed: 1501 additions & 388 deletions

File tree

console-ui/src/pages/Authorizations/Authorizations.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
usePreimageAuthsLoading,
1818
fetchAccountAuthorization,
1919
fetchPreimageAuthorizations,
20+
extentRemainingTransactions,
21+
extentRemainingBytes,
2022
} from "@/state/storage.state";
2123
import { FileUpload } from "@/components/FileUpload";
2224
import { getContentHash, HashAlgorithm, WaitFor, BulletinError } from "@parity/bulletin-sdk";
@@ -54,8 +56,8 @@ function AccountAuthorizationsTab() {
5456
address: searchAddress,
5557
authorization: auth
5658
? {
57-
transactions: BigInt(auth.extent.transactions),
58-
bytes: auth.extent.bytes,
59+
transactions: extentRemainingTransactions(auth.extent),
60+
bytes: extentRemainingBytes(auth.extent),
5961
expiresAt: auth.expiration ?? undefined,
6062
}
6163
: null,
@@ -634,8 +636,8 @@ function FaucetAuthorizeAccountPanel() {
634636
setAuthorization(
635637
auth
636638
? {
637-
transactions: BigInt(auth.extent.transactions),
638-
bytes: auth.extent.bytes,
639+
transactions: extentRemainingTransactions(auth.extent),
640+
bytes: extentRemainingBytes(auth.extent),
639641
expiresAt: auth.expiration ?? undefined,
640642
}
641643
: null
@@ -691,8 +693,8 @@ function FaucetAuthorizeAccountPanel() {
691693
setAuthorization(
692694
auth
693695
? {
694-
transactions: BigInt(auth.extent.transactions),
695-
bytes: auth.extent.bytes,
696+
transactions: extentRemainingTransactions(auth.extent),
697+
bytes: extentRemainingBytes(auth.extent),
696698
expiresAt: auth.expiration ?? undefined,
697699
}
698700
: null

console-ui/src/pages/Dashboard/Dashboard.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import { Spinner } from "@/components/ui/Spinner";
88
import { AuthorizationCard } from "@/components/AuthorizationCard";
99
import { useChainState, useApi, StorageType } from "@/state/chain.state";
1010
import { useSelectedAccount } from "@/state/wallet.state";
11+
import {
12+
extentAllowanceBytes,
13+
extentAllowanceTransactions,
14+
} from "@/state/storage.state";
1115
import { formatAddress, formatBlockNumber, formatBytes, formatNumber } from "@/utils/format";
1216

1317
function QuickActions() {
@@ -294,12 +298,14 @@ function UsageCard() {
294298

295299
for (const { keyArgs, value } of authEntries) {
296300
const extent = value.extent;
301+
const txAllowance = Number(extentAllowanceTransactions(extent));
302+
const bytesAllowance = extentAllowanceBytes(extent);
297303
if (keyArgs[0].type === "Account") {
298-
userAuths.count += Number(extent.transactions);
299-
userAuths.bytes += BigInt(extent.bytes);
304+
userAuths.count += txAllowance;
305+
userAuths.bytes += bytesAllowance;
300306
} else if (keyArgs[0].type === "Preimage") {
301-
preimageAuths.count += Number(extent.transactions);
302-
preimageAuths.bytes += BigInt(extent.bytes);
307+
preimageAuths.count += txAllowance;
308+
preimageAuths.bytes += bytesAllowance;
303309
}
304310
}
305311

console-ui/src/state/storage.state.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,43 @@ export interface PreimageAuthorization {
1515
maxSize: bigint;
1616
}
1717

18+
/**
19+
* Backwards-compatible read of an `AuthorizationExtent`: newer chains track
20+
* consumption separately (`*_allowance` for the cap, `transactions`/`bytes` for
21+
* usage); older chains expose only the cap. The UI surfaces "remaining"
22+
* everywhere, so we compute `allowance - consumed` when both are present and
23+
* fall back to the raw value otherwise.
24+
*/
25+
export function extentRemainingTransactions(extent: any): bigint {
26+
const allowance = extent?.transactions_allowance;
27+
if (allowance != null) {
28+
const used = BigInt(extent.transactions ?? 0);
29+
const cap = BigInt(allowance);
30+
return cap > used ? cap - used : 0n;
31+
}
32+
return BigInt(extent?.transactions ?? 0);
33+
}
34+
35+
export function extentRemainingBytes(extent: any): bigint {
36+
const allowance = extent?.bytes_allowance;
37+
if (allowance != null) {
38+
const used = BigInt(extent.bytes ?? 0n);
39+
const cap = BigInt(allowance);
40+
return cap > used ? cap - used : 0n;
41+
}
42+
return BigInt(extent?.bytes ?? 0n);
43+
}
44+
45+
export function extentAllowanceBytes(extent: any): bigint {
46+
const allowance = extent?.bytes_allowance;
47+
return BigInt(allowance ?? extent?.bytes ?? 0n);
48+
}
49+
50+
export function extentAllowanceTransactions(extent: any): bigint {
51+
const allowance = extent?.transactions_allowance;
52+
return BigInt(allowance ?? extent?.transactions ?? 0);
53+
}
54+
1855
export interface TransactionInfo {
1956
chunkRoot: Uint8Array;
2057
contentHash: Uint8Array;
@@ -45,8 +82,8 @@ export async function fetchAccountAuthorization(
4582
}
4683

4784
const authorization: Authorization = {
48-
transactions: BigInt(auth.extent.transactions),
49-
bytes: auth.extent.bytes,
85+
transactions: extentRemainingTransactions(auth.extent),
86+
bytes: extentRemainingBytes(auth.extent),
5087
expiresAt: auth.expiration ?? undefined,
5188
};
5289

@@ -83,8 +120,8 @@ export async function checkPreimageAuthorization(
83120
}
84121

85122
const authorization: Authorization = {
86-
transactions: BigInt(auth.extent.transactions),
87-
bytes: auth.extent.bytes,
123+
transactions: extentRemainingTransactions(auth.extent),
124+
bytes: extentRemainingBytes(auth.extent),
88125
expiresAt: auth.expiration ?? undefined,
89126
};
90127

@@ -131,7 +168,7 @@ export async function fetchPreimageAuthorizations(
131168
}
132169
return {
133170
contentHash,
134-
maxSize: value.extent.bytes,
171+
maxSize: extentAllowanceBytes(value.extent),
135172
};
136173
});
137174

pallets/transaction-storage/src/benchmarking.rs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ mod benchmarks {
200200
let origin = T::Authorizer::try_successful_origin()
201201
.map_err(|_| BenchmarkError::Stop("unable to compute origin"))?;
202202
let who: T::AccountId = whitelisted_caller();
203-
let transactions = 10;
203+
let transactions: u32 = 10;
204204
let bytes: u64 = 1024 * 1024;
205205

206206
#[extrinsic_call]
@@ -215,13 +215,12 @@ mod benchmarks {
215215
let origin = T::Authorizer::try_successful_origin()
216216
.map_err(|_| BenchmarkError::Stop("unable to compute origin"))?;
217217
let who: T::AccountId = whitelisted_caller();
218-
let transactions = 10;
219218
let bytes: u64 = 1024 * 1024;
220219
let origin2 = origin.clone();
221220
TransactionStorage::<T>::authorize_account(
222221
origin2 as T::RuntimeOrigin,
223222
who.clone(),
224-
transactions,
223+
0,
225224
bytes,
226225
)
227226
.map_err(|_| BenchmarkError::Stop("unable to authorize account"))?;
@@ -273,7 +272,7 @@ mod benchmarks {
273272
let origin = T::Authorizer::try_successful_origin()
274273
.map_err(|_| BenchmarkError::Stop("unable to compute origin"))?;
275274
let who: T::AccountId = whitelisted_caller();
276-
TransactionStorage::<T>::authorize_account(origin, who.clone(), 1, 1)
275+
TransactionStorage::<T>::authorize_account(origin, who.clone(), 0, 1)
277276
.map_err(|_| BenchmarkError::Stop("unable to authorize account"))?;
278277

279278
let period = T::AuthorizationPeriod::get();
@@ -314,13 +313,12 @@ mod benchmarks {
314313
.map_err(|_| BenchmarkError::Stop("unable to compute origin"))?;
315314
let caller: T::AccountId = whitelisted_caller();
316315
let data = vec![0u8; l as usize];
317-
let transactions = 10;
318-
let bytes = l as u64 * 10;
316+
let bytes_allowance = l as u64 * 10;
319317
TransactionStorage::<T>::authorize_account(
320318
origin as T::RuntimeOrigin,
321319
caller.clone(),
322-
transactions,
323-
bytes,
320+
0,
321+
bytes_allowance,
324322
)
325323
.map_err(|_| BenchmarkError::Stop("unable to authorize account"))?;
326324

@@ -340,9 +338,10 @@ mod benchmarks {
340338
.unwrap();
341339
}
342340

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

@@ -355,13 +354,12 @@ mod benchmarks {
355354
let origin = T::Authorizer::try_successful_origin()
356355
.map_err(|_| BenchmarkError::Stop("unable to compute origin"))?;
357356
let caller: T::AccountId = whitelisted_caller();
358-
let transactions = 10;
359-
let bytes = T::MaxTransactionSize::get() as u64 * 10;
357+
let bytes_allowance = T::MaxTransactionSize::get() as u64 * 10;
360358
TransactionStorage::<T>::authorize_account(
361359
origin as T::RuntimeOrigin,
362360
caller.clone(),
363-
transactions,
364-
bytes,
361+
0,
362+
bytes_allowance,
365363
)
366364
.map_err(|_| BenchmarkError::Stop("unable to authorize account"))?;
367365

@@ -382,9 +380,10 @@ mod benchmarks {
382380
.unwrap();
383381
}
384382

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

0 commit comments

Comments
 (0)