Skip to content

Authorization slots#509

Open
franciscoaguirre wants to merge 12 commits into
mainfrom
authorization-slots
Open

Authorization slots#509
franciscoaguirre wants to merge 12 commits into
mainfrom
authorization-slots

Conversation

@franciscoaguirre
Copy link
Copy Markdown
Contributor

@franciscoaguirre franciscoaguirre commented May 7, 2026

With authorization slots a single account can hold multiple authorizations. It's also possible for it to have authorizations in the future. This keeps bulletin much more flexible. Parallel alternative to #469.

Why

The existing single authorization model has a bunch of problems: cross-chain expiry alignment and auto-renewal across boundaries. #469 tightly couples Bulletin to its caller chain via a shared PeriodDuration and a for_period parameter on authorize_account. This branch takes a different cut at the same problems: per-scope BoundedVec<TimedAuthorization, 8> with explicit (starts_at, expiration) per slot, so windows are described locally without cross-chain coupling.

Core design

  • Storage (pallets/transaction-storage/src/lib.rs): Authorizations (single Authorization<BlockNumber> per scope) → AuthorizationSlots: StorageMap<scope, BoundedVec<TimedAuthorization, MaxAuthorizationSlots>>. Each slot carries its own (starts_at, expiration) in relay-chain block numbers and an independent AuthorizationExtent.
  • Time source: new Config::RelayChainBlockNumberProvider (wired to cumulus_pallet_parachain_system::RelaychainDataProvider in both runtimes). Slot windows are relay-block-keyed so they're meaningful across cross-chain authorization flows; the old AuthorizationPeriod config item is gone.
  • Active set: a slot is active when starts_at <= relay_now < expiration. The vec is kept sorted by (expiration ASC, starts_at ASC) for deterministic SCALE encoding and a single forward scan when picking the earliest-expiring active slot.
  • Consumption:
    • store(size) is the soft side: pick any earliest-expiring active slot; saturate bytes and transactions. Over-cap stores still succeed; the priority boost in extension::AllowanceBasedPriority is what demotes them.
    • renew(size) is the hard side: per-slot bytes_permanent + size <= bytes_allowance (no cross-slot subsidy), plus the chain-wide MaxPermanentStorageSize cap. Rejects with PERMANENT_ALLOWANCE_EXCEEDED / CHAIN_PERMANENT_CAP_REACHED respectively.
    • bytes and bytes_permanent are independent axes, both bounded per slot by bytes_allowance. An account granted 10 MiB can store 10 MiB and renew 10 MiB in the same window.
  • Lazy maintenance: prune_expired drops only expired slots — drained slots persist (they can still serve low-priority store() until expiry). The prune skips the storage write when nothing's expired (precheck guard).
  • Additive merge: a new push folds into an existing slot when (a) the windows match exactly OR (b) both slots are already-active (starts_at <= relay_now) and share the same expiration. Pre-clamps the existing slot's saturating bytes / transactions at the old caps so the merge is a pure simplification — the folded extent equals what two separate slots would report.

Extrinsic surface

  • authorize_account(origin, who, transactions, bytes) and authorize_preimage(origin, content_hash, max_size) — unchanged signatures, route through internal helpers and apply the default window (relay_now, relay_now + DefaultAuthorizationWindow). Existing XCM authorizers continue to work without modification.
  • New: authorize_account_window(..., starts_at: Option<u32>, expiration: u32) and authorize_preimage_window(...). starts_at = None means relay_now; explicit windows are validated against MaxStartsAtFuture. A starts_at in the past is accepted (already-active slot).
  • Dropped: refresh_account_authorization / refresh_preimage_authorization. The natural "refresh" is now to push another slot (additive merge handles the common case).
  • New errors: TooManySlots (push would exceed MaxAuthorizationSlots after prune), InvalidWindow, RelayChainTimeUnavailable.

Runtime config (bulletin-westend and bulletin-paseo)

  • runtimes/bulletin-{westend,paseo}/src/storage.rs: DefaultAuthorizationWindow = 14 * DAYS_RELAY = 201_600 (relay 6 s blocks → 14 days), MaxStartsAtFuture = 30 * DAYS_RELAY, MaxAuthorizationSlots = 8. RelayChainBlockNumberProvider = cumulus_pallet_parachain_system::RelaychainDataProvider<Runtime>.
  • runtimes/bulletin-{westend,paseo}/src/lib.rs: STORAGE_VERSION 3 → 4; spec_version 1_000_013 → 1_000_014.

Migration v3 → v4 (multi-block)

  • pallets/transaction-storage/src/migrations.rs::v4::MigrateV3ToV4 is a SteppedMigration modeled on v3::MigrateV2ToV3. Each step drains a few legacy Authorizations entries and translates them.
  • Translation rule: drop entries that are already-expired (in v3's parachain-block clock) or have bytes_allowance == 0. Otherwise emit a fresh slot inheriting (bytes_allowance, transactions_allowance) with (starts_at = relay_now, expiration = relay_now + DefaultAuthorizationWindow) and used counters reset. We don't try to translate v3's parachain-block expiration into a relay-block window — the two clocks don't align losslessly. Pre-existing renewed bytes from the old window remain in PermanentStorageUsed and age out via on_initialize, so resetting per-account bytes_permanent does not double-count.
  • Added to MbmMigrations next to v3::MigrateV2ToV3 in both runtime tuples.

TODO

  • Benchmark weights for the new authorize-window extrinsics and the v3→v4 step.

Comment thread pallets/transaction-storage/src/lib.rs Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants