perf(server): stop re-reading metadata and rings on every allowance call - #366
Open
TarikGul wants to merge 5 commits into
Open
perf(server): stop re-reading metadata and rings on every allowance call#366TarikGul wants to merge 5 commits into
TarikGul wants to merge 5 commits into
Conversation
`allocate_statement_store_allowance` resolved a LitePeople ring before scanning for a slot, so every `statement_create_proof_authorized` paid a `Members.RingKeys` page walk plus `chain_getBlockHash`, `state_getRuntimeVersion` and `CurrentRingIndex` — even when the account already held a slot for the period and nothing needed submitting. The ring is only ever used to build a proof for an extrinsic. Scan for an existing slot first under the `Ignore` policy and return the allowance secret when one is held, resolving the ring only when a submission is actually required. This matches what `allocate_bulletin_allowance` already does: it reads `TransactionStorage.Authorizations` and returns before opening a People-chain connection at all. `slot::find_allocated_slot` is the query form of `scan_slot_excluding` — it ignores free slots, so a table fully occupied by other accounts answers "no slot held" rather than erroring with `NoFreeStatementStoreSlot`. The `Increase` policy is untouched: it always wants an additional slot, so it goes straight to ring resolution as before. One semantic change worth calling out: an account that already holds a slot no longer has to prove ring membership to receive its own allowance key. Membership is what earns a slot, not what makes an already-granted slot usable, so a user whose membership lapses mid-period keeps working until the period rolls over instead of failing with `MissingLitePeopleMembership`.
Both native allowance paths downloaded the full `state_getMetadata` response on every call. For statement-store that download exists only to read one constant — `Resources.LiteStmtStoreSlotsPerPeriod`, the slot count the scan needs — so the steady state pulled the entire runtime metadata to learn a single `u32`. `MetadataCache` keys decoded metadata by genesis hash and revalidates it with `state_getRuntimeVersion`, a small request that only misses across a runtime upgrade. It lives on `RuntimeServices` next to the existing preimage and statement caches, so it is shared by every product runtime built from one host role, and is keyed rather than per-chain-wrapper so the Asset Hub PGAS path can reuse it without a second cache. Combined with scanning before ring resolution, an already-allocated statement-store product call now costs one `state_getRuntimeVersion` plus the slot scan, where it previously cost a metadata download, two chain-state reads, a ring-index read and a `Members.RingKeys` page walk on top of the same scan. `StatementStoreRpc::genesis_hash` and the `RuntimeServices` field are both gated to non-wasm targets: `statement_allowance` is native-only, so on wasm the accessor would be dead code.
…hash
The metadata cache validated its entry with `state_getRuntimeVersion` and
then `fetch_chain_state` asked for the same runtime version again, so the
submission path made two identical requests. Metadata and `ChainState` are
both fixed for a given runtime, so one entry holds both: `MetadataCache`
becomes `ChainContextCache` returning `ChainContext { metadata, state }`,
and the allowance paths no longer call `fetch_chain_state` at all.
`fetch_chain_state` keeps its signature for `truapi-host-cli` and is now
composed from `fetch_genesis_hash` and `fetch_runtime_version`, the same two
requests in the same order.
Filling `ChainState` from the cache key also closes a gap: the genesis hash
baked into every allowance extrinsic came from `chain_getBlockHash(0)` on
whatever connection the host supplied, and nothing checked it against the
chain the caller asked for. A host that wires `connect()` to the wrong chain
produced extrinsics signed for that chain, failing with an opaque validity
error. `ChainContextCache::get` now compares the two and reports
`GenesisHashMismatch` before downloading metadata.
Per allowance-gated product call, the steady state is one
`state_getRuntimeVersion` plus the slot scan; a submission adds the ring
walk and the extrinsic. Neither re-reads metadata or the genesis hash while
the runtime is unchanged.
Records why the cache needs no eviction policy (one entry per configured chain, not per call) and corrects the crate README, which said the allowance paths need no metadata.
Testing against the live paseo-next-v2 People chain showed the previous
commit's genesis check was wrong in both directions.
`network.rs` configures the People genesis as `c5af1826…`, but the chain
reports `89a63b11…`; Asset Hub has likewise diverged (`bf0488db…`
configured, `23e730eb…` live). Only Bulletin still matches. The testnet was
wiped and the constants were never refreshed — the exact failure RFC-0026
exists to remove.
So a divergence does not mean the host connected to the wrong chain, and
rejecting it broke the allowance path on a network where it had been
working. It also had the polarity backwards: `CheckGenesis` is signed over
`ChainState.genesis_hash`, so the value must come from the chain. Using the
configured constant would have produced extrinsics no node accepts.
`ChainContextCache::get` now keys entries by the caller's constant, since
that is the identity it routes connections by, and fills `ChainState` from
what the chain reports. A divergence is logged rather than fatal.
That split also exposed a cache bug the live run confirmed: the insert keyed
by the reported hash while the lookup used the configured one, so on any
network with a stale constant the cache missed on every call — silently
undoing the previous commit. `a_stale_configured_genesis_still_keys_the_cache`
covers it.
`tests/live_people_chain.rs` holds the checks that found this, `#[ignore]`d
so `cargo test` stays offline:
cargo +nightly test -p truapi-host-cli --test live_people_chain \
-- --ignored --nocapture
They confirm the reported genesis reaches `ChainState`, that a second read
hits the cache (`Arc::ptr_eq` on the metadata), that `find_allocated_slot`
scans a live period without erroring, and that live spec 1000032 still
exposes the `AsResources` variant indices the offline fixture pins at spec
1000000 — (2, 1) and (3, 1) in both.
TarikGul
force-pushed
the
tg/allowances-in-core
branch
from
August 11, 2026 19:29
fc8b9b5 to
9ab10b6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Steady state for an allowance-gated product call (
statement_create_proof_authorized,preimage_submit) is onestate_getRuntimeVersionplus the slot scan. A submission addsthe ring walk and the extrinsic. Neither re-reads metadata or the genesis hash while the
runtime is unchanged.
This matches
allocate_bulletin_allowance, which already returns before opening aPeople-chain connection when allowance is in place.
Changes
slot::find_allocated_slot— query form ofscan_slot_excluding; ignores free slots, soa table occupied by other accounts reports "no slot held" rather than erroring. The
Ignorepolicy scans with it first and resolves a ring only when a submission isrequired.
Increaseis unchanged.statement_allowance::ChainContextCacheonRuntimeServices— holdsChainContext { metadata, state }keyed by the caller's configured genesis hash,revalidated with one
state_getRuntimeVersion. One entry per configured chain, so noeviction policy.
ChainState.genesis_hashcomes from the chain, not the caller's constant:CheckGenesisis signed over it, and configured constants drift (paseo-next-v2's People and Asset Hub
hashes are both stale today). Divergence is logged, not fatal.
tests/live_people_chain.rs— read-only live-chain checks,#[ignore]d socargo teststays offline.
sso_responder.rsstatement_allowance.rs(mortalityfield onChainState)sso_responder.rs