Skip to content

feat(core,params): add the amsterdam fork switch - #2756

Open
gzliudan wants to merge 1 commit into
XinFinOrg:dev-upgradefrom
gzliudan:feat-amsterdam-fork-switch
Open

gzliudan wants to merge 1 commit into
XinFinOrg:dev-upgradefrom
gzliudan:feat-amsterdam-fork-switch

Conversation

@gzliudan

@gzliudan gzliudan commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Proposed changes

amsterdamInstructionSet is built but nothing selects it, so the table EIP-8024 landed in (#2268) is dead code: no fork reaches it and the only way in is vm.Config.ExtraEips. This adds the Amsterdam fork switch and wires the two selection points.

The fork is a block number, like every other fork here, not a timestamp as it is upstream. Amsterdam is where the EIPs that did not land in Osaka go; this PR is only the switch, and it schedules nothing.

Summary

The table and its two selection points:

 core/vm/
  ├── jump_table.go          # the table: Osaka + EIP-8024
  ├── evm.go                 # NewEVM picks it
+ │       case evm.chainRules.IsAmsterdam: evm.table = &amsterdamInstructionSet
  └── jump_table_export.go   # LookupInstructionSet picks it
+         case rules.IsAmsterdam: return newAmsterdamInstructionSet(), nil

The fork itself is one entry in the generated source plus the places that name it:

 params/
  ├── config_backfill_fields.json   # source of truth; drives `go generate ./params/`
  │   ├── OsakaBlock      (forkOrder 29)
+ │   └── AmsterdamBlock  (forkOrder 30)
  ├── config.go                     # struct field, String, Description, ActiveForks
  ├── config_forks.go               # IsAmsterdam, Rules.IsAmsterdam
  └── forks/forks.go                # Fork enum, forkToString

The table stays incomplete on purpose. It is Osaka plus EIP-8024 only. EIP-7843 needs a slot number this chain has no source for, having no beacon chain, and EIP-8037 with EIP-8038 reprice state access and replace the single-value gas ledger with a two-dimensional one, so enabling them means porting that ledger first. newAmsterdamInstructionSet records which upstream EIPs are missing and why. They stay with #2730.

No network schedules Amsterdam. XDCMainnetChainConfig, MainnetChainConfig, TestnetChainConfig, DevnetChainConfig and LocalnetChainConfig leave amsterdamBlock unset, and all of them leave osakaBlock unset too, so Amsterdam cannot be scheduled first. The dev and test configs that run every fork from block zero schedule it with Osaka.

chainConfigDigestVersion goes 2 to 3: the canonical semantic encoding covers one more fork field, so digests from an older binary stop matching the fast path and chainConfigJSONEqual falls back to field-level semantic comparison, which still answers correctly, until both sides are re-encoded.

Ref: #2730

Evidence

Every reference to the table, before and after:

Before (dev-upgrade)
  core/vm/jump_table.go:63   amsterdamInstructionSet = newAmsterdamInstructionSet()
  core/vm/jump_table.go:87   func newAmsterdamInstructionSet() JumpTable {
  -> declared and built, never selected

After
  core/vm/jump_table.go:63   amsterdamInstructionSet = newAmsterdamInstructionSet()
  core/vm/jump_table.go:87   func newAmsterdamInstructionSet() JumpTable {
+ core/vm/evm.go:149              evm.table = &amsterdamInstructionSet
+ core/vm/jump_table_export.go:28 return newAmsterdamInstructionSet(), nil
make all                                    # clean
go run build/ci.go test -short -failfast    # clean

Adding a fork field also moves the chain config digest and reaches every place that enumerates forks, so the tests pinning the digest, the tests asserting fork field order, and the tests that push forks out to a future block to keep them off were updated to cover Amsterdam the way they cover Osaka.

The fork-count bookkeeping moves with it too: ActiveForks pre-allocates for the fork it now appends, and params/config_networks_test.go gains the matching Amsterdam entries - the mock config asserts the fork is declared at genesis, and the live-network table asserts it is left unset, which is the guard for the claim that no network schedules Amsterdam.

Several more places needed the same treatment and are in this commit. ActiveForks builds its list in alphabetical order and a test asserts it that way, so Amsterdam is appended first rather than next to Osaka, and that test's fixture now declares the fork so the ordering is pinned. The CheckConfigForkOrder case table gains an amsterdam before osaka case, and TestChainConfigStringIncludesAllFields gains the field and its label so the new String branch is covered. params/config_forks_test.go enumerates the forks twice - once asserting an empty config answers false for every Is* helper, once asserting Description prints a line for every field - so both lists take Amsterdam. Five fixtures that cleared only osakaBlock on a full test config - core/vm/contracts_kzg_stub_test.go, core/blockchain_pruned_ancestors_test.go, core/state_processor_test.go, eth/downloader/testchain_test.go and eth/filters/filter_test.go - now clear amsterdamBlock with it, because clearing Osaka no longer leaves the rules below it. The benchmark-only mirror activeForksLegacyMapSort gains the Amsterdam entry as well, so the legacy_map_sort comparison keeps enumerating the same forks as the list it benchmarks.

CheckConfigForkOrder enforces the ordering the fork is introduced with. The linear check only compares forks that are both set, so a beforeRequired fork-order special case - next to the existing Gas50x constraints - rejects a config that schedules amsterdamBlock while leaving osakaBlock unset; the case table covers it with an amsterdam without osaka case.

Merge Danger

Door: two-way

No network schedules Amsterdam, so nothing on a live chain reads the new field and reverting restores the previous state, digest version included. It stops being reversible once a network schedules Amsterdam and produces blocks under it, which is a separate decision.

Blast Radius: chain config

The only cross-version effect is the digest: a node on this build and one on the previous build comparing chain configs fall back from the digest fast path to field-level comparison, which reports equality correctly. Wire format, header layout, storage layout and the RPC surface are untouched. Within the build the table is selected wherever Amsterdam is scheduled, which today is the dev and test configs.

Types of changes

  • build: Changes that affect the build system or external dependencies
  • ci: Changes to CI configuration files and scripts
  • chore: Changes that don't change source code or tests
  • docs: Documentation only changes
  • feat: A new feature
  • fix: A bug fix
  • perf: A code change that improves performance
  • refactor: A code change that neither fixes a bug nor adds a feature
  • revert: Revert something
  • style: Changes that do not affect the meaning of the code
  • test: Adding missing tests or correcting existing tests

Impacted Components

  • Consensus
  • Account
  • Network
  • Geth
  • Smart Contract
  • External components
  • Not sure (Please specify below)

Checklist

  • This PR has sufficient test coverage (unit/integration test) OR I have provided reason in the PR description for not having test coverage
  • Tested on a private network from the genesis block and monitored the chain operating correctly for multiple epochs.
  • Provide an end-to-end test plan in the PR description on how to manually test it on the devnet/testnet.
  • Tested the backwards compatibility.
  • Tested with XDC nodes running this version co-exist with those running the previous version.
  • Relevant documentation has been updated as part of this PR
  • N/A

The three network-facing items are not applicable: no network schedules Amsterdam and every live network also leaves osakaBlock unset, so there is no chain to run from genesis, no epoch to observe, and no end-to-end plan to give. Backwards compatibility and cross-version coexistence are argued under Merge Danger and covered by the field-level fallback rather than by a network run.

Documentation is a separate PR, #2758, so that this one stays a code change: docs/solidity.md documents the EVM versions a contract can target, and Amsterdam is not a target this chain can serve yet, since the table is missing EIP-7843, EIP-8037 and EIP-8038. It also states the EIP-7954 code and initcode limits, which move with Amsterdam rather than with Osaka, and the tiered gas price floor moves from there to the upgrade notes. The rest of #2730 - SLOTNUM, the 2D gas ledger and the compiler-facing vectors - stays with the issue.

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 8a5b062c-fa90-428e-937f-3595be4204e4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gzliudan
gzliudan force-pushed the feat-amsterdam-fork-switch branch from d87657e to 6ccfa44 Compare October 8, 2026 23:30
@gzliudan gzliudan changed the title feat(params,core/vm): add the amsterdam fork switch feat(core,params): add the amsterdam fork switch Oct 8, 2026
@gzliudan
gzliudan force-pushed the feat-amsterdam-fork-switch branch from 6ccfa44 to 6cb61c3 Compare October 8, 2026 23:39
@gzliudan gzliudan changed the title feat(core,params): add the amsterdam fork switch feat(core,docs,params): add the amsterdam fork switch Oct 8, 2026
@gzliudan
gzliudan force-pushed the feat-amsterdam-fork-switch branch 2 times, most recently from 18b3721 to 7cdb30c Compare October 9, 2026 00:07
@gzliudan gzliudan changed the title feat(core,docs,params): add the amsterdam fork switch feat(core,params): add the amsterdam fork switch Oct 9, 2026
@gzliudan
gzliudan force-pushed the feat-amsterdam-fork-switch branch from 7cdb30c to f820186 Compare October 9, 2026 01:59
@gzliudan
gzliudan force-pushed the feat-amsterdam-fork-switch branch 4 times, most recently from 0df2d82 to cbb82e9 Compare October 9, 2026 04:59
Amsterdam is the fork upstream go-ethereum gives the EIPs that did not land
in Osaka, and this chain has no switch to hang them on: params.ChainConfig
stops at OsakaBlock and nothing selects amsterdamInstructionSet, which
core/vm/jump_table.go builds but no fork ever reaches, so the table built by
EIP-8024 is dead code.

Add AmsterdamBlock as a block-number fork, the way every other fork here is
expressed, together with IsAmsterdam, Rules.IsAmsterdam and the fork order
entry that keeps it after Osaka, and select the table from NewEVM and
LookupInstructionSet.

Amsterdam is Osaka plus the rules that did not land there, so scheduling it
without Osaka would execute rules the chain never switched on. The linear
order check cannot catch that on its own: it compares two forks only when
both are set. CheckConfigForkOrder gains a fork-order special case that
requires Osaka to be set before Amsterdam, and the case table pins it with an
amsterdam-without-osaka case beside the amsterdam-before-osaka one.

No network schedules it: XDCMainnetChainConfig, MainnetChainConfig,
TestnetChainConfig, DevnetChainConfig and LocalnetChainConfig leave
amsterdamBlock unset, and Osaka is unset on all of them, so Amsterdam cannot
be scheduled first. The dev and test configs that run every fork from block
zero schedule Amsterdam with Osaka.

The table stays Osaka plus EIP-8024. EIP-7843 needs a slot number this chain
has no source for, and EIP-8037 and EIP-8038 replace the single-value gas
ledger with a two-dimensional one, so enabling them means porting that ledger
first. newAmsterdamInstructionSet records that they are missing.

Bump chainConfigDigestVersion: the canonical encoding covers one more fork
field, so digests from an older binary stop matching the fast path and
chainConfigJSONEqual falls back to field-level comparison until both sides
are re-encoded. The pinned vectors in core/chainconfig_equal_test.go move
with it.

Ref: XinFinOrg#2730
@gzliudan
gzliudan force-pushed the feat-amsterdam-fork-switch branch from cbb82e9 to d36a2b9 Compare October 10, 2026 14:32

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant