Skip to content

feat(gno.land): meter type-check+preprocess gas at AddPackage and Run#5892

Draft
jaekwon wants to merge 4 commits into
pr1-mempackage-splitfrom
pr2-preprocess-gas
Draft

feat(gno.land): meter type-check+preprocess gas at AddPackage and Run#5892
jaekwon wants to merge 4 commits into
pr1-mempackage-splitfrom
pr2-preprocess-gas

Conversation

@jaekwon

@jaekwon jaekwon commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Stacked on #. Review/merge that one first.

What this does

Deploying a package (MsgAddPackage) or running code (MsgRun) makes every
validator do two expensive steps before anything executes:

  1. type-checking the submitted .gno files (Go's type checker), and
  2. preprocessing them (the VM's own pass).

Today neither step charges any gas. That means a large submission forces a lot
of real CPU work on every node for free — cheap for the sender, expensive for
the network, i.e. a denial-of-service hole.

This PR charges for that work, proportional to how much source was submitted:

  • A new governable chain parameter, PreprocessGasPerByte (default
    1250), charged per byte of .gno source.
  • Charged up front, before the work runs, so an oversized package is
    rejected by the gas meter instead of consuming free CPU first.
  • Applied to both deploying a package and running code.

The charge counts all .gno bytes (production and test files), because
the type checker looks at the test files too — charging only production bytes
would leave part of the work unpaid.

Also included

  • Sensible handling of the new setting on older chains. On a chain whose
    stored settings were written before this parameter existed, the missing value
    is treated as the default (1250) rather than zero — both while running and
    when validating genesis. Without this, a missing value would silently turn the
    charge off, or block unrelated settings changes.
  • Small cleanup: the "package has no production files" check added in PR1 no
    longer makes a throwaway copy of the package just to test it.

Heads up: this changes the app hash and raises gas costs

  • The new parameter's default is written into the chain's genesis settings, so
    the genesis app hash changes. Like PR1, shipping this needs a chain relaunch.
  • Deploy and run transactions now cost more gas (that's the point). Around two
    dozen test fixtures are re-pinned to the new, higher numbers, and the pinned
    app hash is updated.

Testing

  • New parameter tests: validation (must be positive, capped), the legacy-default
    behavior, and genesis tolerating an older settings file.
  • ~24 txtar fixtures re-pinned for the higher gas; the app-hash test updated.
  • All standard suites pass: gno.land/pkg/sdk/vm, the txtar integration suite,
    and the gnovm filetests.

Files changed

  • Gas charge (gno.land): keeper.go (charge in deploy + run), params.go
    (new PreprocessGasPerByte setting + legacy default + validation), genesis.go
    (tolerate an older settings file), params_test.go
  • Generated code: pb3_gen.go, vm.proto (the new setting's wire field)
  • Fixtures: ~24 testdata/*.txtar gas re-pins, plus the app-hash test

Two notes:

  • Value caveat worth stating in the PR: 1250 is a measured central value for typical packages; it deliberately prices the common case and is not a worst-case bound for adversarial type-system inputs (that's a separate concern). It's governable, so it can be tuned later. Worth a line if the reviewers will ask "why 1250."
  • Known gap to mention: this charges the package's own bytes (preprocess + go-type-check). It does not yet charge for re-go-type-checking imported dependencies ; this is omitted because the mid-term plan is to remove go-type-checking as a dependency but could be added in a subsequent pr.

jaekwon and others added 3 commits July 2, 2026 15:35
MsgAddPackage and MsgRun run two native passes over the submitted
package -- go/types type-checking (all .gno files) and gnovm
preprocessing (the prod subset) -- with zero gas charged for either:
unmetered validator CPU, linear in the submitted source bytes.

Add a governable vm param PreprocessGasPerByte (proto field 14,
default 1250, measured ~1250 gas/byte on realistic example packages
at 1 gas == 1ns on the reference machine; validated positive, capped
at 100_000) and charge it per .gno source byte (prod + _test +
_filetest) in AddPackage and Run, up front, so an oversized package
is rejected by the gas meter before the native work runs. The byte
base includes test files because the type-check pass checks them too;
charging prod-only bytes would leave test-heavy packages unmetered.

The non-zero default serializes into genesis vm params state, so the
pinned genesis app hash moves (apphash_crossrealm38_test.go). Txtar
fixtures take -gas-wanted bumps; exact gas pins are re-derived
(addpkg_import_testdep_gas, gnokey_gasfee, restart_gas, maketx_run).

The charge prices the typical case and is deliberately linear; it is
not a worst-case bound for adversarial type-system inputs, and it
does not yet price transitively re-type-checked dependency source (a
planned follow-up adds a per-dep-byte term).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A vm params blob written before the field existed has no
vm:p:preprocess_gas_per_byte key, so GetStruct left the field zero.
That had two consequences on such legacy state: the type-check/
preprocess charge was silently disabled (0 gas per byte), and --
because WillSetParam re-validates the whole struct read via GetParams
-- the first governance update of ANY other vm param panicked with
"PreprocessGasPerByte must be positive" until the new key was set
first.

Zero is otherwise unrepresentable (Validate rejects it on every write
path: SetParams, WillSetParam, ValidateGenesis), so a decoded zero can
only mean pre-field state. Default it to preprocessGasPerByteDefault
in GetParams: the charge stays active on legacy chains and unrelated
param updates validate cleanly. State self-heals on the next SetParams
or genesis export.

NB the sibling IterNextCostFlat has the same latent trap from its own
rollout (pre-field-13 state); left as-is since changing its legacy
behavior (0 -> 1000 per iter step) is out of scope here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two review follow-ups on the mempackage-split branch:

- Genesis/GetParams asymmetry: GetParams defaults a missing (zero)
  PreprocessGasPerByte to 1250 for legacy running state, but
  ValidateGenesis called Params.Validate() directly, which rejects
  zero. A relaunch genesis exported by a binary predating the field
  omits it (decodes as zero), so InitChain would panic — despite the
  branch's migration model being a genesis relaunch. Extract
  Params.applyLegacyDefaults and apply it in GetParams, ValidateGenesis
  (covering the standalone gnoland genesis-validate path too), and
  InitGenesis (so the stored value is the default, not zero). An
  explicitly out-of-range genesis value still fails.

- Drop an allocation in the prod-less AddPackage guard: replace
  MPFProd.FilterMemPackage(memPkg).IsEmpty() (which allocated a full
  filtered copy) with hasProdGnoFile, scanning via MPFProd's own
  per-file FilterGno predicate — same source of truth as the storage
  split, no drift, no copy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jaekwon jaekwon marked this pull request as draft July 2, 2026 23:22
@github-actions github-actions Bot added the 📦 ⛰️ gno.land Issues or PRs gno.land package related label Jul 2, 2026
@Gno2D2

Gno2D2 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

🛠 PR Checks Summary

All Automated Checks passed. ✅

Manual Checks (for Reviewers):
  • IGNORE the bot requirements for this PR (force green CI check)
Read More

🤖 This bot helps streamline PR reviews by verifying automated checks and providing guidance for contributors and reviewers.

✅ Automated Checks (for Contributors):

No automated checks match this pull request.

☑️ Contributor Actions:
  1. Fix any issues flagged by automated checks.
  2. Follow the Contributor Checklist to ensure your PR is ready for review.
    • Add new tests, or document why they are unnecessary.
    • Provide clear examples/screenshots, if necessary.
    • Update documentation, if required.
    • Ensure no breaking changes, or include BREAKING CHANGE notes.
    • Link related issues/PRs, where applicable.
☑️ Reviewer Actions:
  1. Complete manual checks for the PR, including the guidelines and additional checks if applicable.
📚 Resources:
Debug
Manual Checks
**IGNORE** the bot requirements for this PR (force green CI check)

If

🟢 Condition met
└── 🟢 On every pull request

Can be checked by

  • Any user with comment edit permission

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

Labels

📦 ⛰️ gno.land Issues or PRs gno.land package related

Projects

Development

Successfully merging this pull request may close these issues.

3 participants