Skip to content

fix(core/vm,params): fail 0x0a calls instead of faking success - #2666

Merged
gzliudan merged 1 commit into
XinFinOrg:dev-upgradefrom
gzliudan:fix-0x0a-silent-success
Oct 3, 2026
Merged

gzliudan merged 1 commit into
XinFinOrg:dev-upgradefrom
gzliudan:fix-0x0a-silent-success

Conversation

@gzliudan

@gzliudan gzliudan commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

What

0x0a (the EIP-4844 point evaluation precompile) is not implemented: XDC has no blob ecosystem. The address was also missing from every precompile bucket, so a call to it fell through to the empty-account path — success, empty output, and the gas handed to the frame refunded. A contract that checks only the call status would conclude that a KZG proof had been verified.

This registers an always-failing stub for 0x0a in a new PrecompiledContractsPrague bucket and in PrecompiledContractsOsaka.

Behaviour

The stub returns a plain error rather than ErrExecutionReverted, so evm.Call consumes the whole frame — the same thing geth already does for a malformed proof.

It is not a KZG implementation: every input fails, including a well-formed 192-byte proof. It only stops the address from faking success.

Why no activated bucket is touched

Precompile sets are selected by block number, so a single entry added to an already-active bucket would replay every block since that fork under different semantics: gas accounting, account creation and value transfer all change, and any historical call to 0x0a would flip from success to failure.

This fork has no Cancun precompile bucket. Mainnet (cancunBlock 98802000) and Apothem (cancunBlock 71551800) are both past Cancun, but activePrecompiledContracts has no IsCancun case, so they keep resolving precompiles through the EIP1559 bucket, which does not gain the entry. Prague and Osaka are unset on both.

Network chainId Prague Osaka Effect
mainnet 50 unset unset unchanged, 0x0a stays unregistered
Apothem 51 unset unset unchanged, 0x0a stays unregistered
devnet 551 block 50000 unset calls to 0x0a start failing at block 50000
localnet 5151 block 0 unset new behaviour from the genesis block

No activated bucket changes. Devnet is treated as resetable, so the only chain whose behaviour flips is one that gets reset anyway. No network runs the Osaka set, because no named network configuration sets osakaBlock.

Known gaps (deliberately not addressed here)

EIP-2537 (0x0b-0x11) is not provided by the new Prague bucket and is tracked separately in #2668. 0x100 (P256VERIFY, #2694) is out of scope.

Landing order matters. git merge-tree --write-tree, re-verified against the current dev-upgrade, reports a content conflict in core/vm/contracts.go with both #2668 and #2694; params/protocol_params.go merges on its own. #2668 also introduces PrecompiledContractsPrague, and its bucket has no 0x0a entry at all, so whoever rebases second has to re-add the stub by hand. TestKZGPointEvaluationStubSet is the checkpoint to re-run afterwards.

Visibility changes

XDPoS_getConfig reports a KZG_POINT_EVALUATION entry under Prague rules, because params/chainconfigview keys the active precompiles by name. This repository has no eth_config method.

Tracers built on ActivePrecompiles (4byte, flat callTracer, the legacy JS tracers) now classify 0x0a as a precompile under those rules.

core/state_transition.go also passes vm.ActivePrecompiles(rules) into state.Prepare, so under Prague and Osaka rules 0x0a joins the EIP-2929 access list and is warm from the start of every transaction. A first access to the address — BALANCE, EXTCODEHASH, a CALL — is then charged the warm cost (100) instead of the cold one (2600). This is in addition to the call itself now failing, and like the rest of this PR it only takes effect once a Prague or Osaka bucket is active.

Tests

core/vm/contracts_kzg_stub_test.go pins:

  • bucket membership: 0x0a present in Prague/Osaka, absent from Homestead/Byzantium/Istanbul/XDCv2/EIP1559 and from the Cancun-era rules the live networks run under, so the entry can never leak into an activated bucket;
  • bucket composition: Prague/Osaka still carry every entry of the activated EIP1559 bucket, plus the modexp flags (eip2565 from Berlin on, eip7823/eip7883 from Osaka on);
  • rule selection: pinned on the one entry that differs per fork. Prague and Osaka hold the same ten addresses, so the bigModExp variant behind activePrecompiledContracts is what tells the two rule sets apart — without it, pointing the Prague rules at the Osaka bucket would pass every other assertion;
  • behaviour: evm.Call to 0x0a under Prague rules fails with leftOverGas == 0.

Buckets are compared as address sets rather than by length, because a length check cannot tell two buckets apart when they hold the same number of addresses. The price is pinned to the upstream literal (50000), and the stub is exercised with inputs from empty to 200 bytes, including the 192-byte shape a real point evaluation would accept.

Every assertion was mutation-checked: selecting the Prague bucket from Cancun rules, wrong modexp flags, a dropped address, a succeeding Run, a missing registration, an address list of the same length holding a different address, a changed gas literal, an accepted 192-byte input, and pointing the Prague rules at the Osaka bucket each fail a distinct assertion.

One limit is worth stating: the buckets are hand-written literal maps with no canonical address list to compare against, so an entry added to or removed from one of them is only caught by the tests that check composition against the activated EIP1559 bucket. That pattern is inherited from upstream and is not changed here.

make all and make quick-test pass.

@coderabbitai

coderabbitai Bot commented Sep 26, 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: 3782bff1-b285-412e-a9fa-778e333a8d6e

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
  • Autopilot · 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 fix-0x0a-silent-success branch 4 times, most recently from 8fa97f6 to e60373d Compare September 26, 2026 15:49
@gzliudan
gzliudan requested review from AnilChinchawale, anunay-xin, benjamin202410, liam-lai and wanwiset25 and a balanced review from Copilot September 26, 2026 23:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The implementation is well tested, but future-fork precompile routing changes consensus behavior and warrants final human review.

Review effort: Balanced
Findings: None

What changed in this PR

Adds an always-failing 0x0a KZG precompile stub for future Prague and Osaka rules without changing active network semantics.

Changes:

  • Adds Prague precompile routing and address registration.
  • Registers the failing KZG stub in Prague and Osaka.
  • Adds gas constants and focused membership/behavior tests.
File Description
params/​protocol_params.go Defines the stub’s gas cost.
core/​vm/​contracts.go Adds Prague routing and the failing stub.
core/​vm/​contracts_kzg_stub_test.go Tests registration, composition, and failure behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@gzliudan
gzliudan force-pushed the fix-0x0a-silent-success branch 4 times, most recently from b2caad1 to 15c65af Compare October 3, 2026 00:37
@gzliudan gzliudan changed the title fix(core/vm,params): make 0x0a calls fail loudly instead of succeeding silently fix(core/vm,params): fail 0x0a calls instead of faking success Oct 3, 2026
@gzliudan
gzliudan force-pushed the fix-0x0a-silent-success branch from 15c65af to 76bd21f Compare October 3, 2026 01:31
XDC has no blob ecosystem, so the EIP-4844 point evaluation precompile at 0x0a
was never implemented. The address was also missing from every precompile
bucket, so a call to it fell through to the empty-account path: it returned
success with empty output and refunded the gas given to the frame. A contract
that checks only the call status would conclude the KZG proof had been verified.

This registers an always-failing stub for 0x0a in a new Prague bucket and in the
Osaka bucket. The stub returns a plain error rather than ErrExecutionReverted, so
evm.Call consumes the whole frame, which is what geth already does for a
malformed proof. It is not a KZG implementation: every input fails, including a
well-formed 192-byte proof. It only stops the address from faking success.

PrecompiledContractsOsaka is written with the same hexadecimal keys as the new
Prague bucket, so its nine existing entries are rewritten from []byte{1} to
[]byte{0x01} and so on. The values are unchanged, and the hexadecimal spelling is
what upstream geth uses for both buckets.

Restricting the entry to Prague and Osaka is the whole safety argument.
Precompile sets are selected by block number, so a single entry added to an
already-active bucket would replay every block since that fork under different
semantics: gas accounting, account creation and value transfer all change, and
any historical call to 0x0a would flip from success to failure. This fork has no
Cancun bucket at all, mainnet and Apothem still resolve precompiles through the
EIP1559 bucket, and Prague and Osaka are nil on both, so no active bucket
changes. Devnet (chainId 551) schedules Prague at block 50000 and is treated as
resetable, so a devnet past that block is the only chain whose behaviour flips.

EIP-2537 (0x0b-0x11) is still absent from the Prague bucket and is tracked
separately. Whoever adds it must keep the 0x0a entry: the buckets are
hand-written literal maps with no canonical address list to check against.

The tests pin what the above relies on. Buckets are compared as address sets
rather than by length, because Prague and Osaka hold the same ten addresses and a
length check cannot tell them apart; rule selection is pinned on the only entry
that varies per fork, the bigModExp variant behind activePrecompiledContracts
(eip7823/eip7883 from Osaka on), without which pointing the Prague rules at the
Osaka bucket would pass every other assertion. The stub is exercised from empty
input to 200 bytes so a change that starts accepting some inputs fails a test
instead of quietly weakening the guarantee.

docs/solidity.md now describes 0x0a per network instead of the blanket "not
available either" added by XinFinOrg#2689: registered and always failing once the Prague
set is active, unregistered and succeeding as an empty-account call on mainnet
and Apothem.
@gzliudan
gzliudan force-pushed the fix-0x0a-silent-success branch from 76bd21f to 447659f Compare October 3, 2026 01:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The implementation appears coherent, but it changes consensus-sensitive precompile selection and gas semantics and therefore warrants final human review.

Review effort: Balanced
Findings: None

@gzliudan
gzliudan merged commit b00b451 into XinFinOrg:dev-upgrade Oct 3, 2026
14 checks passed
@gzliudan
gzliudan deleted the fix-0x0a-silent-success branch October 3, 2026 01:57
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 3, 2026
…um#30978

Port the EIP-2537 precompiles (0x0b-0x11) from go-ethereum: G1ADD, G1MSM,
G2ADD, G2MSM, PAIRING, MAP_FP_TO_G1 and MAP_FP_TO_G2, along with the
Bls12381 gas constants and the multi-exponentiation discount tables.

ported from geth ethereum#21018, ethereum#29441, ethereum#29445, ethereum#29552, ethereum#30978

The precompiles are added to PrecompiledContractsPrague and to
PrecompiledContractsOsaka, the two buckets upstream registers them in. Both
buckets already exist on dev-upgrade: XinFinOrg#2666 introduced
PrecompiledContractsPrague holding the always-failing 0x0a stub, so 0x0b-0x11
were still empty accounts under Prague rules. The upstream-only
Cacheable/NormalizeInput helpers are dropped because this fork has no
precompile result cache. The 0x0a point evaluation precompile itself is
deliberately not ported, since XDC has no blob ecosystem: the address keeps
the failing stub XinFinOrg#2666 registered, and the 0x100 p256Verify precompile is
still absent.

Test vectors and cases are taken from upstream, including the failure
vectors (testJsonFail/loadJsonFail are introduced for them).
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 3, 2026
…um#30978

Port the EIP-2537 precompiles (0x0b-0x11) from go-ethereum: G1ADD, G1MSM,
G2ADD, G2MSM, PAIRING, MAP_FP_TO_G1 and MAP_FP_TO_G2, along with the
Bls12381 gas constants and the multi-exponentiation discount tables.

ported from geth ethereum#21018, ethereum#29441, ethereum#29445, ethereum#29552, ethereum#30978

The precompiles are added to PrecompiledContractsPrague and to
PrecompiledContractsOsaka, the two buckets upstream registers them in. Both
buckets already exist on dev-upgrade: XinFinOrg#2666 introduced
PrecompiledContractsPrague holding the always-failing 0x0a stub, so 0x0b-0x11
were still empty accounts under Prague rules. The upstream-only
Cacheable/NormalizeInput helpers are dropped because this fork has no
precompile result cache. The 0x0a point evaluation precompile itself is
deliberately not ported, since XDC has no blob ecosystem: the address keeps
the failing stub XinFinOrg#2666 registered, and the 0x100 p256Verify precompile is
still absent.

Test vectors and cases are taken from upstream, including the failure
vectors (testJsonFail/loadJsonFail are introduced for them).
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 3, 2026
…um#30978

Port the EIP-2537 precompiles (0x0b-0x11) from go-ethereum: G1ADD, G1MSM,
G2ADD, G2MSM, PAIRING, MAP_FP_TO_G1 and MAP_FP_TO_G2, along with the
Bls12381 gas constants and the multi-exponentiation discount tables.

ported from geth ethereum#21018, ethereum#29441, ethereum#29445, ethereum#29552, ethereum#30978

The precompiles are added to PrecompiledContractsPrague and to
PrecompiledContractsOsaka, the two buckets upstream registers them in. Both
buckets already exist on dev-upgrade: XinFinOrg#2666 introduced
PrecompiledContractsPrague holding the always-failing 0x0a stub, so 0x0b-0x11
were still empty accounts under Prague rules. The upstream-only
Cacheable/NormalizeInput helpers are dropped because this fork has no
precompile result cache. The 0x0a point evaluation precompile itself is
deliberately not ported, since XDC has no blob ecosystem: the address keeps
the failing stub XinFinOrg#2666 registered, and the 0x100 p256Verify precompile is
still absent.

Test vectors and cases are taken from upstream, including the failure
vectors (testJsonFail/loadJsonFail are introduced for them).
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 3, 2026
…um#30978

Port the EIP-2537 precompiles (0x0b-0x11) from go-ethereum: G1ADD, G1MSM,
G2ADD, G2MSM, PAIRING, MAP_FP_TO_G1 and MAP_FP_TO_G2, along with the
Bls12381 gas constants and the multi-exponentiation discount tables.

ported from geth ethereum#21018, ethereum#29441, ethereum#29445, ethereum#29552, ethereum#30978

The precompiles are added to PrecompiledContractsPrague and to
PrecompiledContractsOsaka, the two buckets upstream registers them in. Both
buckets already exist on dev-upgrade: XinFinOrg#2666 introduced
PrecompiledContractsPrague holding the always-failing 0x0a stub, so 0x0b-0x11
were still empty accounts under Prague rules. The upstream-only
Cacheable/NormalizeInput helpers are dropped because this fork has no
precompile result cache. The 0x0a point evaluation precompile itself is
deliberately not ported, since XDC has no blob ecosystem: the address keeps
the failing stub XinFinOrg#2666 registered, and the 0x100 p256Verify precompile is
still absent.

Test vectors and cases are taken from upstream, including the failure
vectors (testJsonFail/loadJsonFail are introduced for them).
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 3, 2026
…um#30978

Port the EIP-2537 precompiles (0x0b-0x11) from go-ethereum: G1ADD, G1MSM,
G2ADD, G2MSM, PAIRING, MAP_FP_TO_G1 and MAP_FP_TO_G2, along with the
Bls12381 gas constants and the multi-exponentiation discount tables.

ported from geth ethereum#21018, ethereum#29441, ethereum#29445, ethereum#29552, ethereum#30978

The precompiles are added to PrecompiledContractsPrague and to
PrecompiledContractsOsaka, the two buckets upstream registers them in. Both
buckets already exist on dev-upgrade: XinFinOrg#2666 introduced
PrecompiledContractsPrague holding the always-failing 0x0a stub, so 0x0b-0x11
were still empty accounts under Prague rules. The upstream-only
Cacheable/NormalizeInput helpers are dropped because this fork has no
precompile result cache. The 0x0a point evaluation precompile itself is
deliberately not ported, since XDC has no blob ecosystem: the address keeps
the failing stub XinFinOrg#2666 registered, and the 0x100 p256Verify precompile is
still absent.

Test vectors and cases are taken from upstream, including the failure
vectors (testJsonFail/loadJsonFail are introduced for them).
gzliudan added a commit that referenced this pull request Oct 3, 2026
…um#30978 (#2668)

Port the EIP-2537 precompiles (0x0b-0x11) from go-ethereum: G1ADD, G1MSM,
G2ADD, G2MSM, PAIRING, MAP_FP_TO_G1 and MAP_FP_TO_G2, along with the
Bls12381 gas constants and the multi-exponentiation discount tables.

ported from geth ethereum#21018, ethereum#29441, ethereum#29445, ethereum#29552, ethereum#30978

The precompiles are added to PrecompiledContractsPrague and to
PrecompiledContractsOsaka, the two buckets upstream registers them in. Both
buckets already exist on dev-upgrade: #2666 introduced
PrecompiledContractsPrague holding the always-failing 0x0a stub, so 0x0b-0x11
were still empty accounts under Prague rules. The upstream-only
Cacheable/NormalizeInput helpers are dropped because this fork has no
precompile result cache. The 0x0a point evaluation precompile itself is
deliberately not ported, since XDC has no blob ecosystem: the address keeps
the failing stub #2666 registered, and the 0x100 p256Verify precompile is
still absent.

Test vectors and cases are taken from upstream, including the failure
vectors (testJsonFail/loadJsonFail are introduced for them).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants