Repository navigation
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
9a47e10 to
cc9b51b
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The Petersburg policy contradicts its stated upstream behavior, and dual activation fields can produce misleading validation errors.
2 open findings
What changed in this PR
Adds stricter chain-config validation to reject standard fork schedules that omit required predecessors.
Changes:
- Adds generated metadata and sequence validation for standard forks.
- Handles Petersburg/Istanbul fallback activation and Petersburg pairing.
- Expands tests, fixtures, and migration documentation.
| File | Description |
|---|---|
params/config.go |
Runs the new fork-sequence validation. |
params/config_test.go |
Tests accepted and rejected schedules. |
params/config_backfill.go |
Implements sequence and fallback helpers. |
params/config_backfill_test.go |
Pins required forks and fallback behavior. |
params/config_backfill_generator.go |
Generates sequence metadata. |
params/config_backfill_generated.go |
Contains generated fork classifications. |
params/config_backfill_fields.json |
Defines standard and required forks. |
docs/adr/0002-require-standard-fork-sequence.md |
Documents rationale and migration impact. |
miner/worker_test.go |
Completes a worker fixture’s fork prefix. |
core/state_processor_test.go |
Adds Istanbul to a transaction fixture. |
core/genesis_setup_test.go |
Updates genesis-validation fixtures. |
core/genesis_load_test.go |
Updates stored-config fixture setup. |
core/genesis_compat_test.go |
Updates compatibility fixtures. |
core/chain_makers_test.go |
Completes the example fork schedule. |
core/blockchain_test.go |
Completes the EIP-161 fixture. |
cmd/XDC/dao_test.go |
Adds Homestead to the DAO fixture. |
Files not reviewed (1)
- params/config_backfill_generated.go: Generated file
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
cc9b51b to
cd96172
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
It changes startup acceptance for stored consensus configurations and still contains inaccurate behavior and migration documentation.
2 open findings
2 resolved since last review
Files not reviewed (1)
- params/config_backfill_generated.go: Generated file
🧠 Review effort: Balanced
cd96172 to
eb4135a
Compare
… on ethereum#20169 Ref: geth ethereum#20169 (c476460) Refs XinFinOrg#2771 CheckConfigForkOrder compared only pairs of forks that were both configured, so a config that scheduled Osaka while leaving Prague unset was accepted: every fork slot in this chain config is optional, so upstream's non-optional shanghaiTime lock has no counterpart here. Add a second scan over the standard fork slots in forkOrder order. Forks that decide a whole precompile bag or instruction set (Homestead, EIP150, EIP155, EIP158, Byzantium, Petersburg, Istanbul, EIP1559, Prague, Osaka) must be declared before any later standard fork is scheduled; the other standard slots only trigger the check. Both sets live in config_backfill_fields.json and are validated by the generator. Petersburg is required unconditionally, which is stricter than the shape of its own consumer suggests: an unset PetersburgBlock on a chain that never declares ConstantinopleBlock runs the legacy rule either way, but upstream rejects that config, because its petersburgBlock and istanbulBlock entries are non-optional and geth ethereum#21473 states the intent as "fork ordering requirements which state that Petersburg fork be set if Constantinople fork is set". TIPXDCXCancellationFeeBlock stays the effective activation of both Istanbul and Petersburg, mirroring IsIstanbul and IsPetersburg, while TIPXDCXMinerDisableBlock deliberately does not count. That scan reports a gap only when a later slot carries a value, so it cannot see a schedule that stops at ConstantinopleBlock, where IsConstantinople is true while IsPetersburg stays false and the net-metered EIP-1283 SSTORE rule runs with no fork declaring it. chainConfigForkSequencePetersburgPair checks that pair on its own; declaring PetersburgBlock or activating it through TIPXDCXCancellationFeeBlock keeps the config valid. Post-Shanghai, upstream allows a skipped optional fork; this repository is stricter because its instruction sets and precompile bags are selected by flags rather than by the highest configured fork. See docs/adr/0002-require-standard-fork-sequence.md.
eb4135a to
da27f49
Compare


Proposed changes
CheckConfigForkOrderaccepted a chain config that schedules a standard fork while leaving an earlier fork — the one the later forks' rules are selected by — unset.{"chainId": 50, "osakaBlock": 0}passed with Prague, Cancun, EIP1559 and everything before them missing; issue #2771 records the shape and its consequences.This PR adds a second scan over the standard fork slots in
forkOrderorder. Forks that decide a whole precompile bag or instruction set, or whose own flag a cross-cutting rule reads directly (Homestead, EIP150, EIP155, EIP158, Byzantium, Petersburg, Istanbul, EIP1559, Prague, Osaka), must be declared before any later standard fork is scheduled; the other standard slots only trigger the check. Every member of that set is demanded unconditionally, Petersburg included: reading its SSTORE gateIsPetersburg || !IsConstantinoplealone might suggest the slot can be skipped whenConstantinopleBlockis unset, but upstream does not allow that — itspetersburgBlockandistanbulBlockentries are both non-optional, and geth ethereum#21473 states the intent as "fork ordering requirements which state that Petersburg fork be set if Constantinople fork is set". XDC-specific forks stay out of both sets, so the built-in networks are unaffected, andTIPXDCXCancellationFeeBlockremains the effective activation of both Istanbul and Petersburg exactly asIsIstanbulandIsPetersburgdefine it. A schedule that stops atConstantinopleBlockis rejected by a separate pair check (chainConfigForkSequencePetersburgPair), because the scan reports a gap only when a later slot is scheduled:IsConstantinopleis then true whileIsPetersburgstays false, so the net-metered EIP-1283 SSTORE rule runs although no fork declares it. Upstream accepts that shape only because its existence branch needs a later non-optional slot to carry a value, which is the one place where this check is stricter. Both sets are data-driven (config_backfill_fields.json, generator-validated) and the required list is pinned by a test. The rationale, the per-member audit, the Petersburg requirement and the deliberate rejection of a miner-disable-without-Istanbul config are indocs/adr/0002-require-standard-fork-sequence.md.Fixes #2771.
Summary
The scan reports the nearest required predecessor rather than the earliest gap, checks each slot before it updates its state, and never treats the current slot as its own predecessor. The field named as scheduled is the one the value comes from, so a rejection under the cancellation-fee fallback names
TIPXDCXCancellationFeeBlock— the field the operator actually wrote — instead of theIstanbulBlockorPetersburgBlockslot it stands in for:Evidence
dev-upgrade@22a40a58f):{"chainId": 50, "osakaBlock": 0}validates; a config with Cancun or Prague scheduled andEIP158Block/EIP1559Blockunset runs with no code-size cap (CheckMaxCodeSizegates onIsEIP158).After (this branch):
invalid chain config: wrong fork switch order: PragueBlock is unset, but OsakaBlock 0 is scheduled, and the uncapped shapes can no longer start.params/config_test.gopin the exact strings for nine rejections (osaka without prague,cancun without eip1559,prague without eip1559,eip1559 without istanbul,berlin without istanbul, the issue's Shanghai-prefix reproduction,petersburg absent without constantinople,petersburg skipped while constantinople is declaredandpetersburg skipped with nothing later scheduled) and four acceptance boundaries (cancun skipped between eip1559 and prague,petersburg declared after constantinople,petersburg activated through the cancellation fee fork,istanbul activated through the cancellation fee fork), plus the deliberate rejection of a miner-disable-without-Istanbul config. Two more cases guard the scan's own properties: one pins that a config which both inverts a pair and skips a required fork keeps the linear comparison's error, i.e. that the sequence scan stays after it (moving the scan earlier reddens exactly this case), and one pins that the fallback rejection namesTIPXDCXCancellationFeeBlock.TestChainConfigForkSequenceRequiredSetpins the required list — Petersburg included, which is what keeps itsforkSequenceRequiredflag from being dropped — andTestBuiltInChainConfigsPassCheckConfigForkOrderkeeps covering all eleven built-in configs.XDCMainnetChainConfig,TestnetChainConfig,DevnetChainConfigandLocalnetChainConfig, whileMainnetChainConfigstill passes. Dropping Petersburg'sforkSequenceRequiredflag reddensTestChainConfigForkSequenceRequiredSetandpetersburg absent without constantinople, so the flag has its own anchor now that the conditional narrowing is gone. Deleting the pair check reddens exactlypetersburg skipped with nothing later scheduled, and dropping itsConstantinopleBlockguard reddensTestChainConfigForkSequencePetersburgPairInvariant'sconstantinople absentshape andMainnetChainConfigin the built-in-config guard.TestChainConfigForkSequencePetersburgPairInvariantpins the four shapes of the pair, andTestChainConfigForkSequenceFallbackMirrorsRuntimeFlagspins that the slots for which the validation substitutes the cancellation-fee fork are exactly the slots whose runtime flag consults it, so the two copies of that rule cannot drift apart.gofmt/goimportsclean,make all,make quick-test(no failures),go test ./params/... ./core/... ./eth/...without-short(no failures),make tidy(no module change),make generate(no stale files).Merge Danger
Door: two-way for the repository, one-way for an operator whose private chain config is incomplete.
A custom or legacy chain config that schedules a standard fork without declaring the required prefix — or that declares no standard forks at all while relying on a migrated
TIPXDCXCancellationFeeBlock— now fails at startup. All five built-in networks pass unchanged: XDCMainnet (50), Mainnet (1), Testnet (51), Devnet (551) and Localnet (5151) all leaveconstantinopleBlock,petersburgBlockandistanbulBlockunset, and the first four rely onTIPXDCXCancellationFeeBlock(38383838 / 23779191 / 0 / 0) as the effective Petersburg and Istanbul cutoff;MainnetChainConfighas no fallback.IsConstantinoplehas no fallback and stays false on those networks, so Petersburg's condition never reaches them.The one-way part is the restart, not just the first init: a stored config is validated on every startup as well.
SetupGenesisBlockWithOverrideresolves the stored config (with no--genesis,chainConfigOrDefaultreturns it) andresolveSetupStoredConfigResultcallsCheckConfigForkOrderon it,XDPoS.Newchecks the same config again, and the read-onlyLoadChainConfigWithCompatWithOverridepath rejects it too — upstream's config-resolution helper ends incfg.CheckConfigForkOrder()as well. An operator of such a chain therefore cannot restart after upgrading, and the repair is a rule change rather than a config detail: declaringHomesteadBlockchanges the signerMakeSignerpicks (Frontier versus Homestead) and the contract-creation intrinsic gas (21000 versus 53000), and declaringEIP158BlockflipsdeleteEmptyObjects, so the state root of every block changes. Accepting that as a rule change for the chain, or staying on the previous release, are the two honest options.Blast Radius:
paramsstartup validation plus eight test fixtures, thirteen fixture sites, and one more fixture field for Petersburg's unconditional requirement (the pair check added none).The fixtures had to gain the earlier standard forks — the check rejects the shapes they were written as — and two of them, the ones that execute blocks or EVM code, end up running under an upgraded rule set:
TestEIP161AccountRemovalgainsEIP150Block, which turnsrules.IsEIP150on for every block it imports, andTestApplyTransactionWithEVMKeepsCoinbaseFeeRecipientAtTIPTRC21ActivationBlockgainsPetersburgBlockandIstanbulBlock, which turnrules.IsIstanbulon at block 0, the height at which it runs the EVM. Scheduling those completions beyond the blocks each fixture reaches is not an alternative: the linear comparison runs first and rejects it, measured on 2026-10-13 asIstanbulBlock 1000000000 > TIPTRC21FeeBlock 0andEIP150Block 1000000000 > EIP155Block 0.ExampleGenerateChaincompletes its prefix atfutureForkand stays under the rule set it always ran, and the remaining subjects (DAO fork ranges, gas-tier rules, EIP-161 removal, worker pre-commit handling, the XDPoS V2 rejection cases) are unchanged. Those thirteen additions across eight files are written out per fixture rather than shared: the earlier forks are part of each fixture's own setup, and a cross-package helper would need an exported test-only API inparams.#2756/#2757touch the same files from the same base, so whichever lands second needs a trivial rebase ofparams/config*.go. Petersburg's unconditional requirement touches one fixture: thecore/state_processor_test.goconfig that schedulesistanbulBlockfrom genesis now declarespetersburgBlockbeside it, because the shape upstream rejects is rejected here too. The shapes the check separates are pinned as test cases as well. The pair check adds no fixture either — measured with an overlay prototype across./core/... ./miner/... ./cmd/XDC/... ./consensus/... ./eth/... ./internal/ethapi/...(42 packages, zero failures); the one fixture that declaresconstantinopleBlockwithoutpetersburgBlock(core/genesis_setup_test.go) never reaches the check, because its chain ID resolves to the built-in Localnet profile which backfillsTIPXDCXCancellationFeeBlockfirst.ADR numbering: this PR ships
docs/adr/0002-require-standard-fork-sequence.md;0001belongs to #2748 (evict-executed-marker) and0003to the gap ADR that lives outside this branch, so a lone0002is expected until #2748 lands.Types of changes
What types of changes does your code introduce to XDC network?
Put an
✅in the boxes that applyImpacted Components
Which parts of the codebase does this PR touch?
Put an
✅in the boxes that applyChecklist
Put an
✅in the boxes once you have confirmed below actions (or provide reasons on not doing so) thatdocs/adr/0002-require-standard-fork-sequence.md)