Skip to content

(feat): reserved blockspace#2309

Draft
manav2401 wants to merge 23 commits into
reserved-blockspace-block-buildingfrom
manav/reserved-blockspace-merged
Draft

(feat): reserved blockspace#2309
manav2401 wants to merge 23 commits into
reserved-blockspace-block-buildingfrom
manav/reserved-blockspace-merged

Conversation

@manav2401

Copy link
Copy Markdown
Contributor

Summary

More changes for reserved blockspace

Still WIP

Executed tests

<what was actually run beyond CI's standard unit / integration / e2e gates: kurtosis scenarios, chaos runs, manual checks against Amoy / mainnet RPCs, devnet upgrades, etc. Include output or pointers to where the run lives.>

Rollout notes

<consensus-affecting? requires coordinated upgrade? backwards-compatible? operator-facing change?>

kamuikatsurgi and others added 23 commits May 6, 2026 12:45
…-3573)

Reserved-blockspace senders (post-fork, classified via BorConfig) execute
fee-free: no gas debit at buyGas, the EIP-1559 fee-cap floor is waived in
preCheck, no gas refund, no producer tip, and no base-fee burn. The decision
is computed once in newStateTransition so the serial and BlockSTM paths agree
on the same state root. Fee fields stay readable for off-chain settlement.
Post-ReservedBlockspace, CalcBaseFee holds the reserved capacity out of the
gas target and nets the parent's ReservedGasUsed out of gas used, so the
public base fee tracks only normal-region demand against normal-region
capacity. Anchoring the target on reserved capacity (Sigma quotas) rather than
reserved used gas keeps the public market stable and stops a reserved client
from moving the public fee with its own usage. Header.GasLimit is untouched
(formula-only input). VerifyEIP1559Header inherits the change: its pre-Lisovo
path recomputes via CalcBaseFee and its post-Lisovo path is a bounded check.
…-3570)

Reserved-blockspace senders pay zero in-protocol fee, but the pool rejected and
dropped their txs in several places. Add a sender-based, consensus-uniform
reserved branch (the set comes from the chain config, matching the EVM and
base-fee paths):

- validation.go: waive the admission tip floor for reserved senders.
- legacypool Pending: don't cap reserved senders' txs by the miner tip filter,
  so they reach the producer.
- list.Add: reserved senders replace by arrival order (skip the price-bump rule)
  so a stuck zero-fee tx can be cancelled or replaced.

The balance check needs no change: a zero-feeCap tx's Cost() is already just its
value. Sender classification is gated on the ReservedBlockspace fork height.
Factor the reserved branches out of the EVM and consensus hot paths into focused
helpers (reservedZeroFeeGas, calcBaseFeeBurn, verifyReservedFields). Behaviour is
unchanged; this keeps buyGas, execute, and verifyHeader within the cognitive-
complexity budget after the reserved-blockspace additions.
…(POS-3637)

Pins the hardfork-rollout requirement that a scheduled fork prints its
activation height on startup, and that an unscheduled (nil) fork stays silent.
…cks (POS-3570)

The pool admitted reserved zero-fee txs but the producer dropped them: the
price-and-nonce ordering rejects any tx with GasFeeCap below the base fee
(ErrGasFeeCapTooLow). Carry the pool's reserved classification on
LazyTransaction and exempt reserved senders from the base-fee floor in
newTxWithMinerFee (zero miner tip). Caught by a two-node produce/verify
integration test (tests/bor, integration tag): a reserved zero-fee tx is now
mined and cross-verified, the sender pays only its call value, and a
non-reserved zero-fee tx is still rejected at admission.
…(POS-3570)

Review surfaced that several comments described not-yet-implemented work as if
it were live. Correct them so the foundation's deferrals read honestly; no logic
change:

- verifyReservedFields: quota/count value-correctness is enforced by block
  validation in POS-3576, not today — a producer can write any present values.
- setReservedBlockspaceExtraFields: the zero ReservedTxCount/ReservedGasUsed are
  placeholders; the producer's reserved pass (POS-3575) sets the real values.
- CalcBaseFee: the capacity-side target reduction is live while the
  ReservedGasUsed used-side netting stays inert until POS-3575 populates the
  header field, so the public base fee runs slightly hot under reserved load.
  Deterministic (a fee-accuracy gap, not a consensus split); resolves with
  POS-3575, no code change needed here.
- buyGas: note the zero-fee waiver also drops a reserved blob tx's blob fee;
  reserved clients are not a blob use case today.
… onto the foundation branch

Integrates Krishang's reserved-blockspace registry (ReservedBlockspaceRegistry.sol
+ Go reader/registryreader + genesis embed + txpool/miner/core wiring) alongside
our consumer-side foundation (zero-fee EVM, normal-region base fee, txpool/miner
admission, header fields). Clean three-way merge; whole repo builds and both the
reserved-consumer and registry test suites pass.

Post-merge the registry (source of truth) and our config-stub classifier still
run as parallel mechanisms; unifying the consumers onto the registry is the next
step. Contract still needs root()/effectiveFrom/feeMode per the finalized spec.
…kspace registry (POS-3572)

Extend Krishang's ReservedBlockspaceRegistry per the finalized spec (design.md
§4, §7):
- feeMode (0=free, 1=routed) per client + setClientFeeMode; surfaced via
  getClientForAddress.
- effectiveFrom block per client + setClientEffectiveFrom; Bor gates on
  active && effectiveFrom <= number (read at parent state, so deterministic).
- configVersion + root(): a change-epoch bumped on every mutation, so Bor can
  cache its reserved-set snapshot keyed on root() and only rebuild when it moves
  (spec §4.5), avoiding a per-tx state read.

Regenerate the embedded runtime bytecode (forge, solc 0.8.33 pinned), update the
read-only ABI (getClientForAddress now 6 returns + root), and sync the Go reader
(decode feeMode/effectiveFrom, add Root()) + registryreader.ClientLookup/Reader.
11 foundry tests and the Go registry tests pass.
Introduce registryreader.Snapshot — an immutable, pure-lookup view of the
reserved set built once from the registry at a given block state (spec §4.5),
so the hot classification paths (txpool admission, EVM fee-skip, base-fee
capacity) never do a per-transaction state read. BuildSnapshot reads the active
whitelist + per-client feeMode/effectiveFrom/quota + capacity + root(); callers
cache it and rebuild only when root() moves. nil-safe (no registry = classifies
nothing). Extends the Reader interface with WhitelistedAddresses/TotalReservedGas
(already implemented on the concrete reader and the test harness). Snapshot unit
test builds from the real contract bytecode via the harness and verifies
classification, feeMode, capacity, and root.
…fig (POS-3570/3574)

Repoint the pool's reserved classification off the config stub onto the
registry: legacypool builds a registryreader.Snapshot at each reset from the new
head's state, and isReserved / validation.go's tip-floor waiver query the
snapshot (rebuilt per head, not per tx — spec §4.5). The fork-height gate stays
in chain config (a legit chain param); only the reserved *set* now comes from
the registry. TxPool.SetReservedRegistry propagates the reader to subpools.
Reserved unit tests now drive a fake registryreader.Reader as the source; full
txpool suite green.
… in execution (POS-3570/3574)

Build a per-block reserved-set snapshot from the registry at parent state
and inject it into the EVM block context across the serial, V1-parallel,
and V2 BlockSTM execution paths plus the miner. newStateTransition now
classifies reserved senders from the snapshot instead of the config stub,
keeping serial and parallel execution byte-identical.
…stub

CalcBaseFee is pure (config, parent) with no parent state at most call
sites, so the reserved-capacity input cannot read the registry here; it
arrives via a producer-stamped header field (POS-3575/3576), mirroring the
inert reserved-gas-used half. Records the decision so the retained config
stub is deliberate, not an oversight.
…nsensus-safe (POS-3574)

A registry-initialized two-miner devnet exposed two ways reserved-blockspace
classification could differ between the block producer and the verifying
nodes, each a consensus split:

- BuildSnapshot read the registry through the EVM (ApplyMessage), which
  mutates the statedb it runs against. On the execution path the caller passes
  the live block state, so the read leaked gas/nonce/touch changes into the
  block and diverged the state root. Read against a throwaway state copy.

- The serial and V2 block processors and the prefetcher only hold a
  *HeaderChain context, which did not expose the registry reader, so
  ReservedSnapshotForBlock fell through its type assertion to a nil snapshot on
  every import path. Producers (full chain) classified reserved senders and
  built fee-free blocks that verifiers then rejected as underpriced. Mirror the
  reader onto HeaderChain so produce and verify share one source.

Also gate the execution-path snapshot build on the fork height so nodes
syncing from genesis skip the per-block copy and registry read before the
reserved fork activates.

Adds a registryreader unit test (Snapshot accessors, the effectiveFrom
activation boundary, BuildSnapshot error/nil propagation) and converts the
end-to-end integration test to seed the registry via real
initialize()/createClient() txs, asserting a reserved zero-fee tx is admitted,
mined fee-free, and accepted by the peer (cross-node parity), while a
non-reserved zero-fee tx is rejected at admission.
…s (POS-3574)

Multi-agent pre-PR review (/pos-code-review) surfaced fixes worth taking now;
the rest are tracked as pre-activation work in the run report.

- ethapi: bypass the EVM wall-clock timeout for bor-internal system-contract
  reads, not just the gas cap. The reserved-registry snapshot read runs through
  doCall, whose RPCEVMTimeout is node-local and load-dependent. A timeout on one
  node but not another returned a nil snapshot and flipped fee classification,
  diverging the state root. Both node-local limits are now bypassed together for
  internal consensus reads (validators, span, state receiver, registry).

- params: reject scheduling ReservedBlockspaceBlock before CancunBlock (the
  reserved header fields only encode in the post-Cancun extra-data format, while
  verifyHeader requires them once the fork is active), or without a registry
  contract configured. Prevents a rollout height that bricks block production.

- bor: add an interim header-only bound (ReservedGasUsed <= block GasUsed) in
  verifyReservedFields, rejecting an impossible value the base fee would
  otherwise silently clamp. Full value-correctness stays with POS-3576.

- types: GetReservedInfo now routes through DecodeBlockExtraData instead of
  re-decoding the extra blob independently.

- params: drop the dead config-stub classifiers (IsReservedSender/
  ReservedQuotaOf — no production caller; classification is registry-sourced)
  and document that ReservedClients now feeds only the base-fee capacity
  carve-out until the producer-stamped header field lands.

- registryreader: correct the Snapshot/Root/BuildSnapshot docs — the root()-keyed
  cross-block cache is a tracked optimization, not yet implemented on the
  execution path (txpool caches per head; execution rebuilds per block).

Adds tests for the Cancun-order guard, the ReservedGasUsed bound, and the
reserved base-fee capacity-equals-limit boundary.
@manav2401 manav2401 changed the title Manav/reserved blockspace merged (feat): reserved blockspace Jul 16, 2026
@sonarqubecloud

Copy link
Copy Markdown

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.

3 participants