Skip to content

fix(whitelist): fit the caps to one governance call, and benchmark the critical paths on pallet-revive - #359

Open
re-gius wants to merge 7 commits into
masterfrom
re-gius/revive-bench
Open

re-gius wants to merge 7 commits into
masterfrom
re-gius/revive-bench

Conversation

@re-gius

@re-gius re-gius commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Adds a scheduled benchmark of the critical dotNS paths on pallet-revive, and lowers the whitelist caps that it shows don't fit a governance call.

pallet-revive benchmark

Foundry runs the contracts on its own EVM, which doesn't meter weight and proof size the way pallet-revive does, so until now these limits were only checked by hand.

scripts/bench/revive-bench.mjs dry-runs each path as a Root batch against live Asset Hub state at the finalized head, and compares it with the limit it runs under in production, read from the chain:

  • gateway (DotnsGateway.MaxContractCallWeight): device-name issuance (first in a container, later in one, with and without a reservation, and for a user who already has a LabelStore), reservations (fresh, behind left entries, and leaving the front of another queue), and personhood claims(fresh, behind left entries, and into an existing LabelStore);
  • signed (normal-class extrinsic limit): claimLabelStore on first settlement, and expireReservation on a full queue;
  • governance (scheduler maximum weight): a full grantNames batch, and grantName clearing the maximum number of claims, with labels at the 63-byte DNS maximum and reasons at the 256-byte maximum.

With --build, every proxy, and the store beacons through StoreFactory, is first upgraded to this checkout's implementations inside the same dry run, and the whitelist rows run at the build's bounds. Without it, they run at the live caps.

A path fails when it doesn't fit its limit, goes over --fail-at (95% by default), or fails outright. Paths expected to fail for now are listed in KNOWN_FAILURES, each with what clears it: they are reported as known, and the run fails on any other failure, or when a listed path passes.

The workflow runs every Monday and on demand, on Paseo Asset Hub Next and Polkadot Asset Hub, against the deployed contracts and against the build. It has read-only permissions and uses no secrets.

Current results (share of the limit, bold above the threshold):

Path Paseo 1.1.0 Paseo build Polkadot 1.0.0 Polkadot build
Device name + reservation, first in container N/A N/A 111.7% 102.6%
Device name + reservation 70.2% 72.5% 93.8% 74.9%
Device name + reservation, user has a store 77.9% 80.2% 102.2% 83.3%
Device name only 47.5% 49.9% 67.1% 51.6%
Reservation only 27.1% 27.1% 31.5% 28.1%
Reservation behind 4 left entries 29.5% 29.6% 31.5% 30.5%
Reservation leaving the front of another queue 29.0% 29.1% 58.4% 30.0%
Personhood claim 76.3% 79.6% 96.0% 82.4%
Personhood claim behind 4 left entries 79.2% 82.5% 96.0% 85.3%
Personhood claim, user has a store 87.4% 90.7% 107.8% 94.2%
claimLabelStore, first settlement 13.2% 14.9% 13.7% 15.5%
expireReservation, 64 left entries 12.2% 12.2% 1.5% 12.4%
grantNames, live cap / bound 250.0% (100) 90.3% (36) 252.7% (100) 91.3% (36)
grantName clearing claims, live cap / bound 190.1% (64) 90.0% (30) 190.2% (64) 90.1% (30)

Paseo has every device container prepared, so it has no first-in-container row.

Whitelist caps

The live caps let governance queue calls no referendum can execute: 100 names in one grantNames take about 250% of a governance call's weight, and clearing 64 claims about 190%. The bounds governance could raise them to (256 and 128) were higher still.

Default Bound
maxGrantBatch 100 to 32 (80.4% / 81.3%) 256 to 36 (90.3% / 91.3%)
maxClaimants 64 to 24 (72.3% / 72.5%) 128 to 30 (90.0% / 90.1%)

Figures are Paseo / Polkadot, with labels at 63 bytes and reasons at 256 bytes. The bounds are the largest values that stay under the threshold.

Type

  • Bug fix
  • Feature
  • Breaking change
  • Documentation
  • Chore
  • Refactor
  • Security

Scope

  • Registration
  • Resolver
  • Store
  • Proof of Personhood
  • Deployment scripts
  • Tests

Related Issues

Fixes

Closes #287.

Checklist

Code

  • Follows project style
  • forge build passes
  • forge test passes
  • No new compiler warnings

Testing

  • New tests added for changed behavior
  • Fuzz tests added where applicable
  • Invariant tests verified

Security

  • No new selfdestruct or delegatecall
  • Access control reviewed
  • No storage layout conflicts (for upgradeable contracts)

Documentation

  • NatSpec updated on changed interfaces
  • README updated if needed

Breaking Changes

  • No breaking changes
  • Breaking changes documented below

Breaking changes:

How to test

forge test --match-path 'test/*/whitelist/*'
bun scripts/bench/revive-bench.mjs --network paseo
forge build && bun scripts/bench/revive-bench.mjs --network paseo --build

The workflow can only be dispatched once it is on master; its first run will be after merge.

Notes

The new defaults only apply to fresh deployments, since initialize sets them. On Paseo and Polkadot, governance sets the live values with setMaxGrantBatch(32) and setMaxClaimants(24), which the deployed contracts already accept; the new bounds apply after the whitelist is upgraded. The cap only stops new requests, so a name that already holds more claims needs governance to reject some before a grant fits.

Known failures today, all listed in KNOWN_FAILURES:

  • Polkadot 1.0.0: a user who already has a LabelStore can't be issued a device name with a reservation or claim a personhood name through the gateway (102.2% and 107.8%), and the plain claims are over the threshold. The 1.1.0 upgrade clears these.
  • Polkadot, first name in a container: doesn't fit even on the build. Preparing containers 00 to 99, as on Paseo, removes the row.
  • Live whitelist caps: red on both networks until governance lowers them.

On 1.0.0, leaving a queue clears the entry straight away, so the "left entries" rows cost the same as a fresh queue there.

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

CI Summary

Check Result
4naly3er Analysis Found 36 issues: 4 medium, 7 low, 13 gas, 12 informational - View Report
Slither Analysis Found 206 issues: 3 high, 55 medium, 87 low, 59 informational - View Report
Contract Tests (Unit + Fuzz) All tests passed (837 total) - View Report
Contract Tests (Invariant) All tests passed (61 total) - View Report
Gas Report 9 contracts analyzed - View Report
Coverage 77.6% line coverage - View Report
Documentation Passed - 71 pages generated - View Docs
Format & Lint Passed - Code formatted correctly
File Validation Passed - All tracked files valid
Deploy Contracts Reproduces the expected address set; resume verified
PR Title PR Title Valid
Labels Unknown
Secret Scan Passed - No secrets detected

Per-section details omitted: combined body exceeded the 65000-char comment limit. Follow the View Report links above for each section's full output.

@github-actions github-actions Bot added dependencies Pull requests that update a dependency file smartcontracts Any issue relating to smart contracts other type: docs labels Oct 29, 2026
@socket-security

socket-security Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​polkadot/​api@​16.5.69910010088100

View full report

@re-gius re-gius changed the title ci: benchmark the critical paths on pallet-revive fix(whitelist): fit the caps to one governance call, and benchmark the critical paths on pallet-revive Oct 29, 2026
@re-gius
re-gius marked this pull request as ready for review October 29, 2026 10:33
@re-gius
re-gius requested a review from a team as a code owner October 29, 2026 10:33

@GHkrishna GHkrishna left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overall looks good, just a few things:

  • All bench users are fresh, so nobody has a LabelStore and the store-write path never runs. In #348 that path was the worst (91% for a mainnet claim). Could we add rows where the user already has a store (run claimLabelStore in setup), plus one where the user leaves the front of another queue while reserving?
  • Also, since several rows are expected to fail for now, could we either mark them as known failures, so the job only goes red on new failures or regressions, or send failures somewhere we could keep an eye on? Otherwise it’ll be red every Monday and we might miss em.

Please lmk wdyt??

Comment on lines +292 to +296
function leftEntries(base, count) {
const users = Array.from({ length: count }, user);
return {
join: users.map((u) => reserve(u, base)),
leave: users.map((u) => reserve(u, label())),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If I’m reading leftEntries right, each user is at the front when they leave, so the queue gets cleared as they go and the measured call has nothing left to clear. Could we assert reservationMeta shows head < tail with empty entries before measuring? To actually leave 4 empty entries in front, you could queue a front user, then 7 who leave, then the target, and have the front user leave last. Their exit clears 4 and leaves 4 for the measured call.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

On 1.1.0, leaving through a gateway reservation doesn't clear the old queue: _keepOrDropReservation calls _dropReservation with 0 steps, so the entry is deleted where it is and the head doesn't move. After the last user leaves, all 4 emptied entries are still in front, and the measured call clears them. The figures show it: on 1.1.0 the "behind 4 left entries" rows cost more than the fresh ones (29.5% against 27.1%, and 79.2% against 76.3% on Paseo). On 1.0.0 they cost the same, because 1.0.0 cleared an entry on leave.

Comment thread scripts/bench/revive-bench.mjs Outdated
Comment on lines +317 to +318
const grantBatch = Number(await read(WHITELIST, "maxGrantBatch"));
const claimants = Number(await read(WHITELIST, "maxClaimants"));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Out of curiosity could the whitelist rows measure at WHITELIST_MAX_ _LIMIT instead of the live value? That’s what the new bounds promise ig, and it would make the build rows green as soon as the code is right, instead of waiting on governance.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Donein 05fd7d1: with --build, the whitelist rows now run at the build's WHITELIST_MAX_*_LIMIT, read from DotnsConstants

Comment thread scripts/bench/revive-bench.mjs Outdated
Comment on lines +267 to +272
const label = () => {
let n = BigInt(keccak256(toHex(`${seed}-${++counter}`)));
let out = "zq";
for (let i = 0; i < 10; i++, n /= 26n) out += String.fromCharCode(97 + Number(n % 26n));
return out;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The grant rows use 12-character labels. Since the whitelist stores each label, a 40-name batch of long labels could cost noticeably more. Should the bound be measured with max-length labels, the same way reasons use the 256-byte maximum?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch, changed in 05fd7d1 to max-length labels. This showed that length 40 names no longer fit (100.3% on Paseo, 101.4% on Polkadot), so the bound is now 36 (90.3% and 91.3%). The claims bound moved from 32 to 30 (90.0% and 90.1%).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I was wondering what happens when a name has more claims on the live network, would these upper bound still hold?

The cap only stops new requests, so a name with ~30+ existing claims couldn’t be granted in one call. Should we check live claimantCount before, or note that governance would need to reject some first?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also can we add respective dev comments where required with reason like so, "one call fits a referendum’s weight limit" or smthg like this??

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Right, the cap only stops new requests, so a name that collected more claims under a higher cap needs governance to reject some first, one per call, before a grant fits. That's now in the setMaxClaimants NatSpec. Paseo has 4 active names; I'll check Polkadot's claim counts before its upgrade.

Each bound also has a @dev note now: one governance call at the bound still fits the weight a referendum can execute, and the benchmark checks it.

@re-gius

re-gius commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

Overall looks good, just a few things:

* All bench users are fresh, so nobody has a LabelStore and the store-write path never runs. In #348 that path was the worst (91% for a mainnet claim). Could we add rows where the user already has a store (run claimLabelStore in setup), plus one where the user leaves the front of another queue while reserving?

* Also, since several rows are expected to fail for now, could we either mark them as known failures, so the job only goes red on new failures or regressions, or send failures somewhere we could keep an eye on? Otherwise it’ll be red every Monday and we might miss em.

Please lmk wdyt??

Agreed on both, done in 05fd7d1

  • Store path: new rows for a user who already has a LabelStore (setup runs claimLabelStore): the device name with a reservation, and the personhood claim. There's also a row for a reservation that leaves the front of another queue. The store rows shows that on Polkadot, still on 1.0.0, they don't fit the gateway limit (102.2% and 107.8%). On 1.1.0 they come to 83.3% and 94.2%.
  • Known failures: the script now has a KNOWN_FAILURES list, each entry with what clears it. A listed row shows as known, and the run fails on any other failure, or when a listed row starts passing, so the list stays current. I also raised the threshold to 95%. Today we list Polkadot's deployed rows until it runs 1.1.0, the first device name in a container on Polkadot until the containers are prepared, and the deployed whitelist rows until governance lowers the live caps.

@re-gius

re-gius commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

Updated the figures in the PR description: all four runs are from today, on this branch after the merge of #349, with Paseo on 1.1.0, the new store-path and leave-front rows, maximum-length labels, the 95% threshold, and the new bounds (36 names, 30 claims).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file other smartcontracts Any issue relating to smart contracts type: docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat]: Benchmark weights for the container-and-subname PoP lite registration path

2 participants