Skip to content

accounts/abi: filter out unexpected logs from event stream - #35701

Merged
MariusVanDerWijden merged 1 commit into
ethereum:masterfrom
Bue-von-hon:issue-23938
Sep 15, 2026
Merged

MariusVanDerWijden merged 1 commit into
ethereum:masterfrom
Bue-von-hon:issue-23938

Conversation

@Bue-von-hon

@Bue-von-hon Bue-von-hon commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #23938

Rationale

When subscribing to contract events via WatchLogs / WatchEvents, external nodes (e.g. Polygon/Bor or buggy RPC proxies) may occasionally deliver logs that do not match the expected topic filter (such as Sync logs instead of Swap).

Currently, when UnpackLog / unpack returns ErrEventSignatureMismatch, the subscription loop terminates and exits with an error, permanently closing the subscription and causing downstream consumers to fail.

Changes

  • In accounts/abi/abigen/source.go.tpl: ignore bind.ErrEventSignatureMismatch and continue the event loop.
  • In accounts/abi/bind/v2/lib.go: ignore ErrEventSignatureMismatch in WatchEvents and continue the event loop.
  • In accounts/abi/bind/v2/lib_test.go: added TestWatchEventsIgnoreMismatch to verify that mismatched event logs are skipped without terminating the subscription.

@rjl493456442

Copy link
Copy Markdown
Member

It's strange that log provider delivers the unexpected logs, the safe guard looks good to me.

@rjl493456442 rjl493456442 changed the title Fixes #23938 accounts/abi: filter out unexpected logs from event stream Sep 14, 2026
@MariusVanDerWijden
MariusVanDerWijden merged commit c77e2f9 into ethereum:master Sep 15, 2026
9 of 12 checks passed
@MariusVanDerWijden MariusVanDerWijden added this to the 1.17.6 milestone Sep 15, 2026
jimmyc256 pushed a commit to Lambda256/go-ethereum-ralim that referenced this pull request Sep 23, 2026
### Fixes ethereum#23938

### Rationale
When subscribing to contract events via `WatchLogs` / `WatchEvents`,
external nodes (e.g. Polygon/Bor or buggy RPC proxies) may occasionally
deliver logs that do not match the expected topic filter (such as `Sync`
logs instead of `Swap`).

Currently, when `UnpackLog` / `unpack` returns
`ErrEventSignatureMismatch`, the subscription loop terminates and exits
with an error, permanently closing the subscription and causing
downstream consumers to fail.

### Changes
- In `accounts/abi/abigen/source.go.tpl`: ignore
`bind.ErrEventSignatureMismatch` and continue the event loop.
- In `accounts/abi/bind/v2/lib.go`: ignore `ErrEventSignatureMismatch`
in `WatchEvents` and continue the event loop.
- In `accounts/abi/bind/v2/lib_test.go`: added
`TestWatchEventsIgnoreMismatch` to verify that mismatched event logs are
skipped without terminating the subscription.
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Sep 25, 2026
…t signature ethereum#35701

Ported from go-ethereum c77e2f9 (ethereum#35701).

A node or a proxy may hand out a log that does not match the topic filter of a
subscription (upstream issue ethereum#23938). Both the v1 Watch<Event> loop and the v2
WatchEvents loop returned the unpack error, which ends the whole subscription
and takes every event the subscriber was still waiting for with it. Such a log
has to be skipped instead.

Changes:
- accounts/abi/abigen/source.go.tpl: in the Watch<Event> loop, continue when
  UnpackLog reports ErrEventSignatureMismatch.
- accounts/abi/bind/v2/lib.go: same in WatchEvents.
- accounts/abi/bind/v2/watch_events_test.go: the upstream regression test,
  adapted. Upstream keeps it in accounts/abi/bind/v2/lib_test.go, a file this
  repository does not carry, and builds the simulated backend the way
  util_test.go does. The v1 template change carries no behavioural test, just
  like upstream; what covers it is TestBindings compiling a generated v1
  binding, which now calls errors.Is against the error re-exported by
  accounts/abi/bind.
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Sep 25, 2026
…t signature ethereum#35701

Ported from go-ethereum c77e2f9 (ethereum#35701).

A node or a proxy may hand out a log that does not match the topic filter of a
subscription (upstream issue ethereum#23938). Both the v1 Watch<Event> loop and the v2
WatchEvents loop returned the unpack error, which ends the whole subscription
and takes every event the subscriber was still waiting for with it. Such a log
has to be skipped instead.

Changes:
- accounts/abi/abigen/source.go.tpl: in the Watch<Event> loop, continue when
  UnpackLog reports ErrEventSignatureMismatch.
- accounts/abi/bind/v2/lib.go: same in WatchEvents.
- accounts/abi/bind/v2/watch_events_test.go: the upstream regression test,
  adapted. Upstream keeps it in accounts/abi/bind/v2/lib_test.go, a file this
  repository does not carry, and builds the simulated backend the way
  util_test.go does. The v1 template change carries no behavioural test, just
  like upstream; what covers it is TestBindings compiling a generated v1
  binding, which now calls errors.Is against the error re-exported by
  accounts/abi/bind.
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Sep 26, 2026
…t signature ethereum#35701

Ported from go-ethereum c77e2f9 (ethereum#35701).

A node or a proxy may hand out a log that does not match the topic filter of a
subscription (upstream issue ethereum#23938). Both the v1 Watch<Event> loop and the v2
WatchEvents loop returned the unpack error, which ends the whole subscription
and takes every event the subscriber was still waiting for with it. Such a log
has to be skipped instead.

Changes:
- accounts/abi/abigen/source.go.tpl: in the Watch<Event> loop, continue when
  UnpackLog reports ErrEventSignatureMismatch.
- accounts/abi/bind/v2/lib.go: same in WatchEvents.
- accounts/abi/bind/v2/watch_events_test.go: the upstream regression test,
  adapted. Upstream keeps it in accounts/abi/bind/v2/lib_test.go, a file this
  repository does not carry, and builds the simulated backend the way
  util_test.go does. The v1 template change carries no behavioural test, just
  like upstream; what covers it is TestBindings compiling a generated v1
  binding, which now calls errors.Is against the error re-exported by
  accounts/abi/bind.
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Sep 27, 2026
…t signature ethereum#35701

Ported from go-ethereum c77e2f9 (ethereum#35701).

A node or a proxy may hand out a log that does not match the topic filter of a
subscription (upstream issue ethereum#23938). Both the v1 Watch<Event> loop and the v2
WatchEvents loop returned the unpack error, which ends the whole subscription
and takes every event the subscriber was still waiting for with it. Such a log
has to be skipped instead.

Changes:
- accounts/abi/abigen/source.go.tpl: in the Watch<Event> loop, continue when
  UnpackLog reports ErrEventSignatureMismatch.
- accounts/abi/bind/v2/lib.go: same in WatchEvents.
- accounts/abi/bind/v2/watch_events_test.go: the upstream regression test,
  adapted. Upstream keeps it in accounts/abi/bind/v2/lib_test.go, a file this
  repository does not carry, and builds the simulated backend the way
  util_test.go does. The v1 template change carries no behavioural test, just
  like upstream; what covers it is TestBindings compiling a generated v1
  binding, which now calls errors.Is against the error re-exported by
  accounts/abi/bind.
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Sep 28, 2026
…t signature ethereum#35701

Ported from go-ethereum c77e2f9 (ethereum#35701).

A node or a proxy may hand out a log that does not match the topic filter of a
subscription (upstream issue ethereum#23938). Both the v1 Watch<Event> loop and the v2
WatchEvents loop returned the unpack error, which ends the whole subscription
and takes every event the subscriber was still waiting for with it. Such a log
has to be skipped instead.

Changes:
- accounts/abi/abigen/source.go.tpl: in the Watch<Event> loop, continue when
  UnpackLog reports ErrEventSignatureMismatch.
- accounts/abi/bind/v2/lib.go: same in WatchEvents.
- accounts/abi/bind/v2/watch_events_test.go: the upstream regression test,
  adapted. Upstream keeps it in accounts/abi/bind/v2/lib_test.go, a file this
  repository does not carry, and builds the simulated backend the way
  util_test.go does. The v1 template change carries no behavioural test, just
  like upstream; what covers it is TestBindings compiling a generated v1
  binding, which now calls errors.Is against the error re-exported by
  accounts/abi/bind.
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 2, 2026
…t signature ethereum#35701

Ported from go-ethereum c77e2f9 (ethereum#35701).

A node or a proxy may hand out a log that does not match the topic filter of a
subscription (upstream issue ethereum#23938). Both the v1 Watch<Event> loop and the v2
WatchEvents loop returned the unpack error, which ends the whole subscription
and takes every event the subscriber was still waiting for with it. Such a log
has to be skipped instead.

Changes:
- accounts/abi/abigen/source.go.tpl: in the Watch<Event> loop, continue when
  UnpackLog reports ErrEventSignatureMismatch.
- accounts/abi/bind/v2/lib.go: same in WatchEvents.
- accounts/abi/bind/v2/watch_events_test.go: the upstream regression test,
  adapted. Upstream keeps it in accounts/abi/bind/v2/lib_test.go, a file this
  repository does not carry, and builds the simulated backend the way
  util_test.go does. The v1 template change carries no behavioural test, just
  like upstream; what covers it is TestBindings compiling a generated v1
  binding, which now calls errors.Is against the error re-exported by
  accounts/abi/bind.
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 2, 2026
…t signature ethereum#35701

Ported from go-ethereum c77e2f9 (ethereum#35701).

A node or a proxy may hand out a log that does not match the topic filter of a
subscription (upstream issue ethereum#23938). Both the v1 Watch<Event> loop and the v2
WatchEvents loop returned the unpack error, which ends the whole subscription
and takes every event the subscriber was still waiting for with it. Such a log
has to be skipped instead.

Changes:
- accounts/abi/abigen/source.go.tpl: in the Watch<Event> loop, continue when
  UnpackLog reports ErrEventSignatureMismatch.
- accounts/abi/bind/v2/lib.go: same in WatchEvents.
- accounts/abi/bind/v2/watch_events_test.go: the upstream regression test,
  adapted. Upstream keeps it in accounts/abi/bind/v2/lib_test.go, a file this
  repository does not carry, and builds the simulated backend the way
  util_test.go does. The v1 template change carries no behavioural test, just
  like upstream; what covers it is TestBindings compiling a generated v1
  binding, which now calls errors.Is against the error re-exported by
  accounts/abi/bind.
gzliudan added a commit to XinFinOrg/XDPoSChain that referenced this pull request Oct 2, 2026
…t signature ethereum#35701 (#2628)

Ported from go-ethereum c77e2f9 (ethereum#35701).

A node or a proxy may hand out a log that does not match the topic filter of a
subscription (upstream issue ethereum#23938). Both the v1 Watch<Event> loop and the v2
WatchEvents loop returned the unpack error, which ends the whole subscription
and takes every event the subscriber was still waiting for with it. Such a log
has to be skipped instead.

Changes:
- accounts/abi/abigen/source.go.tpl: in the Watch<Event> loop, continue when
  UnpackLog reports ErrEventSignatureMismatch.
- accounts/abi/bind/v2/lib.go: same in WatchEvents.
- accounts/abi/bind/v2/watch_events_test.go: the upstream regression test,
  adapted. Upstream keeps it in accounts/abi/bind/v2/lib_test.go, a file this
  repository does not carry, and builds the simulated backend the way
  util_test.go does. The v1 template change carries no behavioural test, just
  like upstream; what covers it is TestBindings compiling a generated v1
  binding, which now calls errors.Is against the error re-exported by
  accounts/abi/bind.
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 18, 2026
These twelve generated files were last written in January 2025, before the v1
abigen template was rebuilt by the ethereum#31379 backport and before the template fixes
that landed here with XinFinOrg#2645 (geth ethereum#25574), XinFinOrg#2628 (geth ethereum#35701) and XinFinOrg#2631
(geth ethereum#32164). Regenerating now brings all of them in at once, which is why it
was held back until those three were merged.

The input is the ABI and bytecode already committed in these files, so the
payload is unchanged byte for byte and only the shape of the generated code
moves forward: XxxMetaData is added and XxxABI/XxxBin become deprecated
aliases, Deploy* and bind* use MetaData.GetAbi() instead of re-parsing the ABI
JSON on every call, library linking no longer substitutes into the shared
package-level XxxBin, the Parse<Event> helpers appear, and the Watch<Event>
loops skip a log whose signature does not match instead of ending the whole
subscription.

One API reduction: types with no bytecode, namely the abstract interfaces
ITRC21, AbstractXDCXListing, AbstractTokenTRC21, LAbstractRegistration,
LAbstractTokenTRC21 and LAbstractXDCXListing, no longer get XxxBin and
DeployXxx, because the template guards both on a non-empty bytecode input.
Nothing in the tree references them.

Regenerating means running the in-tree abigen against a combined-json document
rebuilt from the checked-in ABI and bytecode. Two details matter when doing so:
the combined-json "bin" field must hold bare hex, since the template prepends
the 0x itself, and XDCValidatorBin is the one literal in these files that has no
0x prefix.

Verified with make all and make quick-test (127 packages, no failures).
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 18, 2026
These twelve generated files were last written in January 2025, before the v1
abigen template was rebuilt by the ethereum#31379 backport and before the template fixes
that landed here with XinFinOrg#2645 (geth ethereum#25574), XinFinOrg#2628 (geth ethereum#35701) and XinFinOrg#2631
(geth ethereum#32164). Regenerating now brings all of them in at once, which is why it
was held back until those three were merged.

The input is the ABI and bytecode already committed in these files, so the
payload is unchanged byte for byte and only the shape of the generated code
moves forward: XxxMetaData is added and XxxABI/XxxBin become deprecated
aliases, Deploy* and bind* use MetaData.GetAbi() instead of re-parsing the ABI
JSON on every call, library linking no longer substitutes into the shared
package-level XxxBin, the Parse<Event> helpers appear, and the Watch<Event>
loops skip a log whose signature does not match instead of ending the whole
subscription.

One API reduction: types with no bytecode, namely the abstract interfaces
ITRC21, AbstractXDCXListing, AbstractTokenTRC21, LAbstractRegistration,
LAbstractTokenTRC21 and LAbstractXDCXListing, no longer get XxxBin and
DeployXxx, because the template guards both on a non-empty bytecode input.
Nothing in the tree references them.

Regenerating means running the in-tree abigen against a combined-json document
rebuilt from the checked-in ABI and bytecode. Two details matter when doing so:
the combined-json "bin" field must hold bare hex, since the template prepends
the 0x itself, and XDCValidatorBin is the one literal in these files that has no
0x prefix.

Verified with make all and make quick-test (127 packages, no failures).
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 18, 2026
These twelve generated files were last written in January 2025, before the v1
abigen template was rebuilt by the ethereum#31379 backport and before the template fixes
that landed here with XinFinOrg#2645 (geth ethereum#25574), XinFinOrg#2628 (geth ethereum#35701) and XinFinOrg#2631
(geth ethereum#32164). Regenerating now brings all of them in at once, which is why it
was held back until those three were merged.

The input is the ABI and bytecode already committed in these files, so the
payload is unchanged byte for byte and only the shape of the generated code
moves forward: XxxMetaData is added and XxxABI/XxxBin become deprecated
aliases, Deploy* and bind* use MetaData.GetAbi() instead of re-parsing the ABI
JSON on every call, library linking no longer substitutes into the shared
package-level XxxBin, the Parse<Event> helpers appear, and the Watch<Event>
loops skip a log whose signature does not match instead of ending the whole
subscription.

One API reduction: types with no bytecode, namely the abstract interfaces
ITRC21, AbstractXDCXListing, AbstractTokenTRC21, LAbstractRegistration,
LAbstractTokenTRC21 and LAbstractXDCXListing, no longer get XxxBin and
DeployXxx, because the template guards both on a non-empty bytecode input.
Nothing in the tree references them.

Regenerating means running the in-tree abigen against a combined-json document
rebuilt from the checked-in ABI and bytecode. Two details matter when doing so:
the combined-json "bin" field must hold bare hex, since the template prepends
the 0x itself, and XDCValidatorBin is the one literal in these files that has no
0x prefix.

Verified with make all and make quick-test (127 packages, no failures).
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 18, 2026
These twelve generated files were last written in January 2025, before the v1
abigen template was rebuilt by the ethereum#31379 backport and before the template fixes
that landed here with XinFinOrg#2645 (geth ethereum#25574), XinFinOrg#2628 (geth ethereum#35701) and XinFinOrg#2631
(geth ethereum#32164). Regenerating now brings all of them in at once, which is why it
was held back until those three were merged.

The input is the ABI and bytecode already committed in these files, so the
payload is unchanged byte for byte and only the shape of the generated code
moves forward: XxxMetaData is added and XxxABI/XxxBin become deprecated
aliases, Deploy* and bind* use MetaData.GetAbi() instead of re-parsing the ABI
JSON on every call, library linking no longer substitutes into the shared
package-level XxxBin, the Parse<Event> helpers appear, and the Watch<Event>
loops skip a log whose signature does not match instead of ending the whole
subscription.

One API reduction: types with no bytecode, namely the abstract interfaces
ITRC21, AbstractXDCXListing, AbstractTokenTRC21, LAbstractRegistration,
LAbstractTokenTRC21 and LAbstractXDCXListing, no longer get XxxBin and
DeployXxx, because the template guards both on a non-empty bytecode input.
Nothing in the tree references them.

Regenerating means running the in-tree abigen against a combined-json document
rebuilt from the checked-in ABI and bytecode. Two details matter when doing so:
the combined-json "bin" field must hold bare hex, since the template prepends
the 0x itself, and XDCValidatorBin is the one literal in these files that has no
0x prefix.

Verified with make all and make quick-test (127 packages, no failures).
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 19, 2026
These twelve generated files were last written in January 2025, before the v1
abigen template was rebuilt by the ethereum#31379 backport and before the template fixes
that landed here with XinFinOrg#2645 (geth ethereum#25574), XinFinOrg#2628 (geth ethereum#35701) and XinFinOrg#2631
(geth ethereum#32164). Regenerating now brings all of them in at once, which is why it
was held back until those three were merged.

The input is the ABI and bytecode already committed in these files, so the
payload is unchanged byte for byte and only the shape of the generated code
moves forward: XxxMetaData is added and XxxABI/XxxBin become deprecated
aliases, Deploy* and bind* use MetaData.GetAbi() instead of re-parsing the ABI
JSON on every call, library linking no longer substitutes into the shared
package-level XxxBin, the Parse<Event> helpers appear, and the Watch<Event>
loops skip a log whose signature does not match instead of ending the whole
subscription.

One API reduction: types with no bytecode, namely the abstract interfaces
ITRC21, AbstractXDCXListing, AbstractTokenTRC21, LAbstractRegistration,
LAbstractTokenTRC21 and LAbstractXDCXListing, no longer get XxxBin and
DeployXxx, because the template guards both on a non-empty bytecode input.
Nothing in the tree references them.

Regenerating means running the in-tree abigen against a combined-json document
rebuilt from the checked-in ABI and bytecode. Two details matter when doing so:
the combined-json "bin" field must hold bare hex, since the template prepends
the 0x itself, and XDCValidatorBin is the one literal in these files that has no
0x prefix.

Verified with make all and make quick-test (127 packages, no failures).
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 20, 2026
These twelve generated files were last written in January 2025, before the v1
abigen template was rebuilt by the ethereum#31379 backport and before the template fixes
that landed here with XinFinOrg#2645 (geth ethereum#25574), XinFinOrg#2628 (geth ethereum#35701) and XinFinOrg#2631
(geth ethereum#32164). Regenerating now brings all of them in at once, which is why it
was held back until those three were merged.

The input is the ABI and bytecode already committed in these files, so the
payload is unchanged byte for byte and only the shape of the generated code
moves forward: XxxMetaData is added and XxxABI/XxxBin become deprecated
aliases, Deploy* and bind* use MetaData.GetAbi() instead of re-parsing the ABI
JSON on every call, library linking no longer substitutes into the shared
package-level XxxBin, the Parse<Event> helpers appear, and the Watch<Event>
loops skip a log whose signature does not match instead of ending the whole
subscription.

One API reduction: types with no bytecode, namely the abstract interfaces
ITRC21, AbstractXDCXListing, AbstractTokenTRC21, LAbstractRegistration,
LAbstractTokenTRC21 and LAbstractXDCXListing, no longer get XxxBin and
DeployXxx, because the template guards both on a non-empty bytecode input.
Nothing in the tree references them.

Regenerating means running the in-tree abigen against a combined-json document
rebuilt from the checked-in ABI and bytecode. Two details matter when doing so:
the combined-json "bin" field must hold bare hex, since the template prepends
the 0x itself, and XDCValidatorBin is the one literal in these files that has no
0x prefix.

Verified with make all and make quick-test (127 packages, no failures).
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 20, 2026
These twelve generated files were last written in January 2025, before the v1
abigen template was rebuilt by the ethereum#31379 backport and before the template fixes
that landed here with XinFinOrg#2645 (geth ethereum#25574), XinFinOrg#2628 (geth ethereum#35701) and XinFinOrg#2631
(geth ethereum#32164). Regenerating now brings all of them in at once, which is why it
was held back until those three were merged.

The input is the ABI and bytecode already committed in these files, so the
payload is unchanged byte for byte and only the shape of the generated code
moves forward: XxxMetaData is added and XxxABI/XxxBin become deprecated
aliases, Deploy* and bind* use MetaData.GetAbi() instead of re-parsing the ABI
JSON on every call, library linking no longer substitutes into the shared
package-level XxxBin, the Parse<Event> helpers appear, and the Watch<Event>
loops skip a log whose signature does not match instead of ending the whole
subscription.

One API reduction: types with no bytecode, namely the abstract interfaces
ITRC21, AbstractXDCXListing, AbstractTokenTRC21, LAbstractRegistration,
LAbstractTokenTRC21 and LAbstractXDCXListing, no longer get XxxBin and
DeployXxx, because the template guards both on a non-empty bytecode input.
Nothing in the tree references them.

Regenerating means running the in-tree abigen against a combined-json document
rebuilt from the checked-in ABI and bytecode. Two details matter when doing so:
the combined-json "bin" field must hold bare hex, since the template prepends
the 0x itself, and XDCValidatorBin is the one literal in these files that has no
0x prefix.

Verified with make all and make quick-test (127 packages, no failures).
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Oct 20, 2026
These twelve generated files were last written in January 2025, before the v1
abigen template was rebuilt by the ethereum#31379 backport and before the template fixes
that landed here with XinFinOrg#2645 (geth ethereum#25574), XinFinOrg#2628 (geth ethereum#35701) and XinFinOrg#2631
(geth ethereum#32164). Regenerating now brings all of them in at once, which is why it
was held back until those three were merged.

The input is the ABI and bytecode already committed in these files, so the
payload is unchanged byte for byte and only the shape of the generated code
moves forward: XxxMetaData is added and XxxABI/XxxBin become deprecated
aliases, Deploy* and bind* use MetaData.GetAbi() instead of re-parsing the ABI
JSON on every call, library linking no longer substitutes into the shared
package-level XxxBin, the Parse<Event> helpers appear, and the Watch<Event>
loops skip a log whose signature does not match instead of ending the whole
subscription.

One API reduction: types with no bytecode, namely the abstract interfaces
ITRC21, AbstractXDCXListing, AbstractTokenTRC21, LAbstractRegistration,
LAbstractTokenTRC21 and LAbstractXDCXListing, no longer get XxxBin and
DeployXxx, because the template guards both on a non-empty bytecode input.
Nothing in the tree references them.

Regenerating means running the in-tree abigen against a combined-json document
rebuilt from the checked-in ABI and bytecode. Two details matter when doing so:
the combined-json "bin" field must hold bare hex, since the template prepends
the 0x itself, and XDCValidatorBin is the one literal in these files that has no
0x prefix.

Verified with make all and make quick-test (127 packages, no failures).
gzliudan added a commit to XinFinOrg/XDPoSChain that referenced this pull request Oct 20, 2026
These twelve generated files were last written in January 2025, before the v1
abigen template was rebuilt by the ethereum#31379 backport and before the template fixes
that landed here with #2645 (geth ethereum#25574), #2628 (geth ethereum#35701) and #2631
(geth ethereum#32164). Regenerating now brings all of them in at once, which is why it
was held back until those three were merged.

The input is the ABI and bytecode already committed in these files, so the
payload is unchanged byte for byte and only the shape of the generated code
moves forward: XxxMetaData is added and XxxABI/XxxBin become deprecated
aliases, Deploy* and bind* use MetaData.GetAbi() instead of re-parsing the ABI
JSON on every call, library linking no longer substitutes into the shared
package-level XxxBin, the Parse<Event> helpers appear, and the Watch<Event>
loops skip a log whose signature does not match instead of ending the whole
subscription.

One API reduction: types with no bytecode, namely the abstract interfaces
ITRC21, AbstractXDCXListing, AbstractTokenTRC21, LAbstractRegistration,
LAbstractTokenTRC21 and LAbstractXDCXListing, no longer get XxxBin and
DeployXxx, because the template guards both on a non-empty bytecode input.
Nothing in the tree references them.

Regenerating means running the in-tree abigen against a combined-json document
rebuilt from the checked-in ABI and bytecode. Two details matter when doing so:
the combined-json "bin" field must hold bare hex, since the template prepends
the 0x itself, and XDCValidatorBin is the one literal in these files that has no
0x prefix.

Verified with make all and make quick-test (127 packages, no failures).
benjamin202410 added a commit to XinFinOrg/XDPoSChain that referenced this pull request Oct 27, 2026
* fix(accounts/abi/abigen): guard v2 UnpackError against short revert data (#2719)

The UnpackError method generated by abigen --v2 indexes raw[:4] without
checking the length first, so revert data shorter than 4 bytes (e.g. an
empty revert) makes the call panic instead of returning "Unknown error".

Return the unknown-error result early when the data is too short to hold
a selector, and regenerate the committed v2 bindings that carry custom
errors (solc_errors) so they stay in sync with the template.

Upstream go-ethereum has the same pattern (its source2.go.tpl still
indexes raw[:4] after 4a305af1) and has no fix for it, so this change is
made independently without referencing an upstream PR.

* test(accounts/abi/bind,accounts/abi/bind/v2): pin the MetaData ABI cache (#2722)

* test(accounts/abi/bind): pin the MetaData ABI cache

MetaData.GetAbi backs the abigen v1 binding constructors: every
New<Contract>Caller, New<Contract>Transactor and New<Contract>Filterer call it,
so the parsed ABI is supposed to be computed once and handed out from the cache
afterwards. Those call sites come from the abigen v1 template and reach this tree
with the regenerated v1 bindings (#2784); until that lands the cached path has no
caller here and only the test below exercises it. Nothing in this package pinned
that down, as it carried no test file, so a bypassed cache, a cached parse
failure, a cache shared between bindings and an unguarded concurrent first call
would all have gone unnoticed.

Four cases cover those four halves of the contract. The first calls GetAbi twice
and requires the same *abi.ABI back, and also checks the parse result, so that a
cache returning one shared error or one zero value cannot pass. The second
requires a rejected ABI to report an error, to return no ABI, to leave the cache
empty, and to parse successfully once the ABI is corrected. The third gives two
bindings different ABIs and requires each of them to keep its own parsed result,
and then gives a third binding the first ABI again to require that two instances
carrying the same ABI still get one parsed result each. The fourth releases 32
goroutines at once onto an empty cache, over 16 rounds, and requires all of them
to get the same instance back.

Every case was checked for discriminating power by mutating GetAbi through
go test -overlay: dropping the early return of the cached pointer fails the
first and fourth cases, caching an empty ABI on a failed parse fails the second,
and handing out a package-level singleton while still reporting parse errors
fails the second and third. Dropping the mutex fails the fourth under the race
detector and, without the race detector, in the large majority of runs, because
the pointer check depends on how the 32 callers interleave. A package-level
cache keyed on the ABI string instead of on the instance fails the third case as
well, at the same-ABI assertion.

No production code changes. gofmt, goimports, go vet and go test -race pass on
this package, and make all and make quick-test pass for the tree.

* test(accounts/abi/bind/v2): pin the MetaData ABI cache

MetaData.ParseABI is the v2 counterpart of the v1 GetAbi pinned in the previous
commit, and the two are the same contract line for line: hold the mutex, hand
out the cached *abi.ABI once parsed, and leave the cache empty when the ABI does
not parse. Generated v2 bindings keep their MetaData in a package-level variable
and call this method from every New<Contract> constructor, the instance wrappers
then reusing the parsed ABI it caches, so the same four regressions are possible
here: a bypassed cache, a cached parse failure, a cache shared between bindings
and an unguarded concurrent first call.

The package had no test for any of it. The four cases mirror the v1 ones, with
the second ABI carrying another method name so that a cache ignoring its
receiver cannot pass, and the third also handing a third binding the first ABI
again so that two instances carrying the same ABI must still parse one result
each.

Discriminating power was checked by mutating ParseABI through go test -overlay:
dropping the early return fails the first and fourth cases, caching an empty ABI
on a failed parse fails the second, handing out a package-level singleton while
still reporting parse errors fails the second and third, and dropping the mutex
fails the fourth under the race detector. A package-level cache keyed on the ABI
string instead of on the instance fails the third case as well, at the same-ABI
assertion.

No production code changes. gofmt, goimports, go vet and go test -race pass on
accounts/abi/bind/v2, and make all and make quick-test pass for the tree.

* fix(internal/ethapi): preserve access lists with gasPrice (#2713)

* fix(internal/ethapi): preserve access lists with gasPrice

* test(ethapi): document access-list regression cases

* test(internal/ethapi): align access-list regression coverage

Move the access-list regression next to the existing FillTransaction tests.
Use the same parallel setup, explicit sender and AddrLocker, and verify the
transaction type, gasPrice and access list after decoding the returned raw
transaction. Drop the duplicate gasPrice-only control; the existing
TestFillTransactionLegacyWhenGasPriceProvided already covers that case.

The production fix remains a deliberate local divergence from inherited
geth behavior. No upstream fix was identified at the reviewed geth snapshot
f8f9bc574459a1afefac7b63163739910ee0fe62. The inherited comment was
"Make it possible to default to newer tx, but use legacy if gasprice is
provided". Here an explicit access list must survive the gasPrice override.

Keep the explicit legacy/access-list selection. The shorter guard does not
cover the CallDefaults/ToTransaction path used by eth_simulateV1. CallDefaults
can fill zero dynamic-fee fields alongside gasPrice; these and the dynamic
default select type 2 before the access-list branch. Set-code defaults or
an authorizationList also take precedence. A local diagnostic passes five
call-conversion cases with the existing override; the shorter guard fails
the four access-list cases while preserving the no-access-list control.
These are not all valid FillTransaction inputs, and the diagnostic is kept
outside the patch. Do not add a fee-style change to this narrow correction.

---------

Co-authored-by: Supersnips <48771887+SupersnipsXDC@users.noreply.github.com>

* fix(eth/filters): restore pending filter hash responses #26757 (#2714)

* fix(eth/filters): restore pending filter hash responses

* test(filters): document pending-filter response regressions

* test(eth/filters): document nil api.sys precondition

Explain why the hash-only pending tests do not need a filter backend,
and when they must use newTestFilterSystem. No executable code changes.

---------

Co-authored-by: Supersnips <48771887+SupersnipsXDC@users.noreply.github.com>

* fix(core/rawdb): return the iterator error from InspectDatabase (#2723)

ported from go-ethereum #32506

InspectDatabase walked the keyspace and never consulted it.Error(), so a walk
that the database aborted printed a table built from a partial scan and returned
nil, which reads as "inspection passed" to whoever ran db inspect. Only the
per-key accounting was affected; nothing on the consensus or sync paths reads
this function, its single caller is the db inspect command.

Upstream landed the check as part of parallelising the traversal, so its
InspectDatabase is a worker closure over per-range stat structs driven by an
errgroup. The version here is the single-threaded one and the two do not line
up line for line, so only the semantics are ported: report the pending
iterator error once the walk ends. The defect predates dev-upgrade, hence a
separate commit rather than a fixup.

The test also pins the user-visible half of the early return, by capturing
os.Stdout around the call: an aborted walk must render nothing, and a clean walk
over the same fixture must still render the report, down to its per-category
rows and a non-zero total. Upstream orders it the same way - its workers
return the iterator error and eg.Wait short-circuits before the table is
rendered - but upstream has no test for either function, so the
assertion is ours. One direction alone cannot tell an early return apart from a
function that never renders anything. Confirmed with go test -overlay, both
directions: deleting the check fails the first assertion with got <nil>, moving
it below table.Render() fails the second one on the table built from the partial
scan. The capture helper drains the pipe from its own goroutine and restores
os.Stdout through t.Cleanup, so the walk cannot deadlock against the pipe buffer.

The aborted-walk fixture is shared with
TestDeleteDanglingHashesIteratorError, which landed with #2707; it lives in
accessors_chain_test.go, so this test keeps no copy of its own. The zero-total
assertion counts the total rows it recognises and fails when it finds none, so a
column layout it no longer matches cannot leave the check silently vacuous.

* fix(cmd/utils): size the trie dirty cache from --cache-gc in MakeChain (pre-existing) (#2726)

MakeChain wrote the --cache-gc percentage into cache.TrieCleanLimit, one
statement after --cache-trie had written the same field, so on the MakeChain
path (XDC import/export and the MakeChain tests) --cache-trie was silently
ignored and --cache-gc ended up sizing the clean trie cache.

SetEthConfig maps the same flags the other way round: --cache-trie sizes the
clean cache and --cache-gc the dirty one, which is also what the flag help
texts describe, so MakeChain now agrees with it.

The line goes back to the #18087 port (#946, b9a6c8c32d): that commit split the
old cache.TrieNodeLimit into TrieCleanLimit and TrieDirtyLimit, pointed the
dirty flush limit at TrieDirtyLimit in core, and rewrote this very statement,
which had to become TrieDirtyLimit, as TrieCleanLimit. Upstream #18087
(434dd5bc00) rewrote the same statement to TrieDirtyLimit, so there is no
upstream fix to port: --cache.gc has always sized the dirty limit there.

The cache assembly moves into makeCacheConfig to make the mapping testable.
With --cache 20088 --cache-trie 20 --cache-gc 5 the clean limit is 4017 MB
(instead of 1004 MB) and the dirty limit 1004 MB (instead of the 256 MB
default). The mapping is pinned by TestMakeCacheConfigSplitsTrieCachesByFlag
(defaults, the split, either flag on its own, --cache on its own, archive mode
and prefetch) and the GC mode guard by TestMakeCacheConfigRejectsInvalidGCMode.

The --cache-trie and --cache-gc help text parentheticals are corrected in the
same commit: ac2f09f169f (#2501) rebalanced the values when the total
allowance went from 1024 to 3072 MB (cache-trie 15 -> 5, cache-gc 25 -> 34)
but left the Usage strings advertising the old defaults, so --help showed
percentages the flags no longer had. The archive-mode parentheticals are
inherited from go-ethereum, where they are equally static, so they are left
untouched to stay upstream-faithful.

* fix(core): fix pre-existing nil state reaching the prefetcher (#2727)

When insertChain cannot open the parent state for the followup-block prefetch,
NewWithChainConfig returns a nil StateDB, and the prefetch carried that nil
straight into its goroutine, where IntermediateRoot dereferenced it. A panic in
a goroutine cannot be recovered, so the node died instead of importing the
block without prefetching - the crash reported as #1738.

The hazard is pre-existing rather than introduced here: the discarded error
came in with the concurrent prefetch itself, and the earlier fix for #1738
(#1742) removed a variable-capture race in the same block but left the error
discarded, so a nil still reached the prefetcher whenever the parent state
could not be opened.

Prefetching is best effort, so the prefetch now starts only when the state
opens, and Prefetch returns early when it is handed a nil state; no caller can
take the node down this way. CacheConfig.TrieCleanPrefetch is fed by the
--cache-prefetch flag, either directly (makeCacheConfig in cmd/utils/flags.go)
or through eth.Config.Prefetch, which SetEthConfig writes from that same flag
(eth/backend.go); the flag has no default value, so the default behaviour is
unchanged. A [Eth] Prefetch key in a config file is not an effective input,
because SetEthConfig overwrites it.

go-ethereum cleared the same hazard when it rewrote the prefetcher for in-block
prefetching (#31557): it creates the throwaway state with a checked error and
never hands nil to the goroutine. This fork still carries the older shape, so
this is a local guard rather than a port.

TestStatePrefetcherToleratesNilStateDB calls Prefetch with a nil state and
reproduces the #1738 stack without the guard, so the test pins the fix.

* fix(core,ethapi,simulated): stop assuming the engine is XDPoS, close #2708 (#2740)

* fix(core): skip the masternode refresh when the engine is not XDPoS

UpdateM1At reports ErrNotXDPoS when the config names XDPoS while the engine is not *XDPoS.XDPoS, and both of its callers turned any error into log.Crit, i.e. os.Exit(1): the head refresh inside adoptHead and the reorg apply loop. A chain built through the exported NewBlockChain and SetChainConfig with an XDPoS config and another engine therefore died on its first gap block. A masternode set has no meaning without the XDPoS engine, so both sites now skip the refresh on ErrNotXDPoS and keep halting on every other failure, which is the contract the head-refresh site documents.

The defect is pre-existing: the adoptHead shape came in with the #2566 line and the reorg site is as old as the reorg refactor, so this is a new commit on top of dev-upgrade rather than a fixup. go-ethereum has no XDPoS engine and no masternode refresh, so there is nothing to port.

Reachability: CreateConsensusEngine derives the engine from the same chainConfig it hands to the chain, so a normally started node cannot build the pair and nothing changes on mainnet, Apothem or devnet. The exported NewBlockChain plus SetChainConfig can, which is the combination the issue reports.

* fix(internal/ethapi): return no signed block when the engine is not XDPoS

findNearestSignedBlock asserted api.b.Engine() to *XDPoS.XDPoS inline, so a chain whose config names XDPoS while its engine is not XDPoS panicked there instead of reporting that it has no checkpoint to find. Only the XDPoS engine has epoch switches, so there is nothing for another engine to find; the helper now returns no signed block, which its caller findFinalityOfBlock already answers as finality 0. Nothing changes for a chain whose config and engine agree, which is every chain a normally started node builds.

The site is reachable through the exported core.NewBlockChain plus SetChainConfig: CreateConsensusEngine derives the engine from the same config and cannot build the pair. go-ethereum has no counterpart - it has no XDPoS engine and no epoch-switch query - so there is nothing to port.

* fix(ethclient/simulated): answer instead of asserting the XDPoS engine

The finalized-block branch of the simulated filter backend asserted fb.bc.Engine() to *XDPoS.XDPoS after checking the config alone, so a chain whose config names XDPoS v2 while its engine is not XDPoS would panic there. Naming XDPoS v2 in the config does not make the engine XDPoS, and the engine is what holds the committed block info; the branch now answers the same error it already returns for a v1 chain.

The site is not reachable today: New routes a config that names XDPoS to newXDCSimulatedBackend, whose engine is XDPoS.NewFaker, so the pair cannot be built through it. It is guarded for consistency with the invariant the issue tracks rather than as a live defect. go-ethereum has no counterpart, so there is nothing to port.

* fix(core): fix pre-existing method-value block number in the gap refresh log

refreshMasternodesForGapBlock logs block.Number, a method value, in its crit
field, so the number it reports is a function address. The two inline crit
sites the helper replaced already read that way, so the slip predates this
branch; the warn field the branch added is fixed in the commit that adds it.

The helper is XDC-specific and has no upstream counterpart, so there is no geth
PR to reference.

* fix(cmd/XDC): open the chain database once in the import command #21414 (#2749)

XDC import built a full node first, which opened chaindata, and then opened the
same database a second time through utils.MakeChain. LevelDB takes an exclusive
lock on the database directory, so the second, writable open failed and the
command exited before reading the file it was given, on every datadir and for
every input file:

    Fatal: Could not open database: resource temporarily unavailable

Build a config node instead, the way export, dump and the other chain commands
already do, so that MakeChain is the only opener. That also collapses the
duplicate SetupMetrics call that makeFullNode followed by importChain made.

Skipping makeFullNode also leaves out the XDCx service registration, the
xdc/info gauge and the ethstats service it used to set up. None of them changes
what this command does - the import chain comes from MakeChain's own engine, and
the command never starts the node (stack.Start() is never called).

One consequence is visible: this fork's RegisterEthService is where --syncmode
light is rejected ("light mode has been deprecated"), so a light-mode import used
to fatal before it read the database and now runs the import instead.

Upstream's MakeChain takes only the stack, so it can discard the config node's
result; here the chain config mismatch policy comes from the same config, so the
bound cfg has to be kept.

Upstream's #21414 shipped no test either and nothing upstream covers this
command's database open, so no regression test comes with this change. It was
verified by hand on this branch's binary, against two throwaway datadirs:

    XDC --datadir A init testnet
    XDC --datadir A --testnet export chain.rlp   # 669 bytes, genesis only
    XDC --datadir B --testnet import chain.rlp   # exit 0

where chaindata is opened once (521.00MiB) and "Importing blockchain" is reached.
Restoring makeFullNode on the line above and rebuilding reproduces the original
failure on the same three steps - XDCx 128.00MiB, then chaindata 768.00MiB from
the eth service, then chaindata 521.00MiB from MakeChain - and then:

    Fatal: Could not open database: resource temporarily unavailable   # exit 1

The pull request records a replay of a 152-block export on a private network,
which is what covers imported data rather than the open itself.

Ref: #21414

* feat(core,rawdb,adr): evict the executed marker with the state it guards (#2748)

The marker #2566 introduced is written for every block this node executes and nothing
evicted it: one key per executed block, ~0.9 GB on a chain of 107.5 M blocks. Give it the
same lifetime as the state it guards.

HasExecutedBlock is a conjunction, and below head-TriesInMemory the second half decides it:
the write path drops the state of those heights from memory instead of writing it, so on the
heights whose root no longer resolves the marker is not an answer there. The eviction
therefore rides the trie garbage collector's own cursor in writeBlockWithState, next to the
Dereference that ends the state: no sweep, no cursor, no throttle and no background
goroutine, and nothing is scanned.

What tells the two lifetimes apart is the state itself, asked of the trie database after the
dereference rather than assumed from the height. A root a flush committed - the one above
the loop, an earlier one, or a Cap - stays readable on disk, and a root another queued
height still references stays readable in memory, so on those heights HasBlockAndFullState
rests on the same read the probe makes, apart from the TIPXDCX conjuncts, and the marker is
the half that decides. They are not one height per call: a height whose header names a root
the flush wrote - a block without transactions and without rewards - is readable for the
same reason, and dereferencing a committed root takes nothing off disk for it. Probing is
what makes an absent marker unable to change any answer, for any caller, on every height
rather than only on the one the cursor happens to stand on.

The queue has a second exit, and it takes no markers with it while the head sits at the last
block the write path executed: saveData drains, at shutdown, what the write path had not
reached yet, but every height still queued then sits inside the state window - the write
path pops every entry at or below its cursor before it breaks - and there the marker still
decides - over the state saveData has just committed to disk on head, head-1 and
head-(TriesInMemory-1), and, on the rest, over a root that is readable on disk - one of
those three, one a chain of blocks without transactions shares with them, or one a Cap left
there. Taking those markers would turn "this node ran the block" into an execution for every
height whose state outlives the restart, so the drain dereferences the roots and carries the
cursor as a guard rather than a cut: the write path pops every entry at or below it, so no
queued height can reach the delete and the drain takes no marker on that line. A head
adopted past the last written block - writeKnownBlock moves the head without a write - can
leave queued entries below the drain's line, and that arm takes their markers with the roots
it dereferences, cutting by its cursor instead of asking the state: the one place the two
lifetimes can part company. It is recorded in the ADR rather than solved here. One window of
markers is left behind per clean shutdown, and nothing later takes them.

An archive node never dereferences state, so there the marker decides all the way down and
every marker is kept. Evicting there is what would need a window, a persisted cursor and a
pass over the receipts key range, and it would make a range an archive node still holds the
state for be executed again rather than recognised.

Markers a database already carries are not swept, because the eviction names only the
heights the queue still holds: an upgraded node bounds its growth from here on rather
than reclaiming what is behind it, and the inert keys below the line go only when SetHead
or a re-import rewrites those heights.

bc.triegc carries the block hash alongside the state root so the eviction can name the
marker's key without a read, and DeleteExecutedMarker now returns its refusal:
DeleteReceipts and the rewind keep treating it as fatal, while the two garbage-collector
exits report and skip it - behind the cursor, so the cost is one stale marker and not the
block.

Decision and rejected alternatives:

docs/adr/0001-bound-executed-marker-to-state-lifetime.md

* test(cmd/XDC): wait for the RPC endpoint instead of a fixed sleep #20053 (#2751)

The attach tests slept a fixed two seconds before dialling the freshly
started node. Under a parallel test run, node startup (opening the two
leveldb caches, genesis setup, the blockchain recovery check and the
txpool constructors) can take longer than that, so the attach raced the
listener and failed with "connection refused".

Poll the endpoint with rpc_modules until it answers, with a two minute
budget, the same way geth does it in cmd/geth/run_test.go. Each attach
test now finishes as soon as the endpoint is up (about 0.2s) instead of
always burning two seconds.

The poll can now fail before the attach is even attempted, and t.Fatal
would skip the trailing Interrupt/ExpectExit pair, leaving the node
running. The three tests therefore register t.Cleanup(XDC.Kill), which
kills the node on that path too.

Ref: #20053

* chore(skills): add use-modern-go skill from JetBrains (#2774)

Add the JetBrains go-modern-guidelines skill to .agents/skills/use-modern-go
so it is available as a project-level skill in this repository.

- Invoke via the use-modern-go skill (slash command or use_skill).
- Guidelines are checked against a target Go version with
  scripts/run-tool.sh list --go-version <ver>.
- .agents/ is globally gitignored, so the directory is force-added
  (git add -f); it stays tracked thereafter.

Tooling only; no runtime/source changes.

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

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 live 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, and the version byte is part of it, so the digest moves and the pinned
vectors in core/chainconfig_equal_test.go are re-pinned with it. The digest is
only ever computed and compared in-process, so no cross-version fallback is
involved.

Ref: #2730

* fix(core,params): move the eip-7954 code size raise to amsterdam #33832 #35217 (#2757)

EIP-7954 raises the contract code and initcode size limits, and upstream
go-ethereum has always gated it on Amsterdam: MaxCodeSizeAmsterdam arrived
with the EIP itself, and #35217 later raised it from 32768 to 65536 to match
the EIP as it stands. The port here hung the limit on Osaka instead and kept
the first value, so Osaka on this chain would have carried a rule upstream
Osaka does not have, at half the size upstream Amsterdam allows now, and
Amsterdam had nothing to carry when it was finally added.

Gate the limit on IsAmsterdam and adopt the current upstream values, 65536 of
code and 131072 of initcode. Osaka falls back to the 24576 and 49152 it has
upstream, which is what makes the two Osakas equivalent again.

No network schedules either fork, so no chain changes behaviour today. The
dev and test configs that run every fork from block zero run Amsterdam, so
the oversized-initcode case in createGasTests moves past the new limit: a
create2 has to exceed 131072 now, not 65536, to be rejected. The fixture in
core/state_processor_test.go runs Osaka without Amsterdam, so its too-big
initcode comes from params.MaxInitCodeSize, the 49152 those rules allow, and
the conditional that used to hand it the Osaka limit goes away.

Ref: #33832 #35217

* docs(solidity,upgrade): document amsterdam, eip-2935 and block.basefee (#2758)

* docs(upgrade): normalise the wrapping and structure of the upgrade notes

The file mixed hard-wrapped paragraphs with single-line ones, so editing it
meant re-wrapping by hand, and every later change to a wrapped paragraph showed
up as a diff across the lines it happened to span. Put every paragraph, bullet
and numbered item on its own line; 187 continuation lines are joined.

That same pass restores structure the wrapping had flattened. Nine bullets in the
presence-tracking list carried their four labelled sub-items as indented text on
the bullet's own line, and the same shape appears in three groups further down,
where each parent is a plain paragraph rather than a list item; those labelled
lines are now a list under it, without a markdown-level nesting.
The six steps of the chain-config upgrade procedure, which the wrapping had run
together into one paragraph around two commands, are a list, and the sentence
that introduces the second command stays plain text inside the item it belongs
to rather than becoming an item of its own.

The runtime choices for the four networks named each network twice, once for its
flag and once for its notes; each network is one bullet now, with its startup
flag and its notes as sub-items.

Existing fenced blocks, tables and list indentation are untouched. This is not a
whitespace-only change: the list markers are new.

* docs(solidity): rewrite the gas price notes against block.basefee

The minimum gas price the pool accepts is the gas price of the block's tier,
the same value `block.basefee` reports, and the two were documented a section
apart: the special variable under `## Special variables`, the pool floor in a
top-level `## Gas price` section of the same file.

Say it once, in the section that already explains what the tier is. The floor
sentence, the per-network table and the `eth_gasPrice` advice move under
`### block.basefee`, and the standalone section goes away. The sentence also
records that transactions to the block-signer and randomize system contracts are
exempt from the floor, and the file loses the blank line the removed section had
left at the end.

The two paragraphs this commit edits in place, the pool-acceptance sentence and
the `blobbasefee` note, also move to one line per paragraph. The rest of the
rewrap stays with the commit that documents the Amsterdam evm version.

* docs(solidity): rewrap the notes and document the amsterdam evm version

Amsterdam is a fork this chain now has a switch for and a target the compiler
started exposing in 0.8.36, but the compatibility table stops at Osaka and never
mentions it, so a reader cannot tell whether `--evm-version amsterdam` artifacts
work here. They do not: the fork is not implemented (EIP-7843, EIP-8037 and
EIP-8038 are missing) and no network has scheduled it.

Add an Amsterdam column that is TBD on every network, and a note recording that
0.8.36 began exposing the target and 0.8.37 added `block.slotnum` and
`slotnum()`, both only with `--evm-version amsterdam`, still experimental and
not the compiler default. The code and initcode limits EIP-7954 raises belong to
Amsterdam rather than Osaka, so the size-limit section states them too. The note
says the switch exists while the instruction set does not cover the target,
because at that point the evm version is reachable in the table but not in the
compiler.

The file also moves to one line per paragraph, matching the format rule the rest
of the change follows; the only wording that pass touches is note 3, which now
points at note 7.

* docs(solidity): document the prague eip-2935 history contract

The upgrade notes describe where the EIP-2935 history contract is deployed and
how it is backfilled, which is what an operator needs. What a contract can do
with it was not written down anywhere: `docs/solidity.md` documents the special
variables a contract reads, and the history contract is the way to read a block
hash older than the 256 `blockhash` answers for.

Add that half. `blockhash` is unchanged, the contract keeps the last 8191
hashes, reading one means calling it with the block number, and a number outside
that window reverts rather than returning zero. The deployment and backfill
rules stay in `docs/upgrade.md`, which this commit does not touch, so the two
files split by audience rather than duplicating each other.

* docs(upgrade): correct the eip-2935 deployment and backfill notes

The section described a backfill of recent parent hashes at upgrade time, which neither the code nor EIP-2935 does: block processing deploys the contract at the activation block, writes that block's parent hash into one slot, and every later block adds one more, so the rest of the window reads back as zero until 8,191 blocks have passed.

Document that route together with the upstream one, where the account is created by the presigned deployment transaction the EIP specifies and clients carry no deployment code of their own, and record the two further local deviations in the failure handling and the gas budget.

* docs(solidity): drop the stale prague premise from note 5

The note still asked the reader to imagine Prague activating "in the future", while the activation table in the same file has carried devnet at block 50000 and the Prague section added alongside it describes what is already scheduled.

* docs(solidity): add the testnet gas2500x tier to the base fee table

The table named one tier fork per network, but testnet also has Gas2500xBlock set at 83600000, where the floor moves from 12.5 GWei to 625 GWei. The prose already described that transition; the table an operator looks up did not.

* docs(upgrade): generalise the fork declaration requirement

The section named Prague alone, but osakaBlock and amsterdamBlock join the
same rule: both are part of the built-in backfill set and are not inherited
by custom-network hydration, so they need the same explicit declaration.
Naming the backfill-set forks explicitly keeps the rule readable as forks
are added.

* docs(solidity,upgrade): fix pre-existing spelling and quotation slips

Both slips predate this branch. `implemention` in the `block.prevrandao`
and `block.blobbasefee` notes is present at the base commit (lines 61 and
85 there), and the "semantically different" sentence in the upgrade notes
already used curly quotes (line 203 there). The gas-price and rewrap
commits in this branch reproduced both verbatim, so they arrive unchanged.

Fix the spelling and match the straight quotes used everywhere else in
both files; no other wording changes.

* docs(solidity): align the activation table with the scheduled testnet prague

#2811 scheduled Prague on testnet at 87633000, so the TBD in that cell is
stale, and the TBD convention itself needs stating: with mainnet unset and
testnet scheduled, TBD can only mean that no activation block is scheduled.
Title the table accordingly and widen the Prague column to hold the
eight-digit block.

The precompile note repeats the stale premise in its Apothem clause, and three
sentences claimed "no network" / "every network configuration" / "everywhere"
while the dev and test fixtures schedule Amsterdam and Osaka from genesis. All
now say "real network".

Ref: #2811

* docs(solidity,upgrade): fix pre-existing inconsistencies in the upgrade notes

Five inconsistencies predate this branch and survived the earlier passes
because each one reads correctly on its own:

- The backfill-source table says a custom network only gets the historical
  fork fields, while the list below it also names the migrated
  system-contract addresses that BackfillCustomMigratedFieldsFrom copies.
- The strict-validation preamble asks for "non-zero usable values", but
  fork switches accept 0 (active from genesis) and only the
  system-contract addresses are checked for zero.
- The checklist is written in Go field names while its reader verifies a
  genesis.json, so state once that the JSON keys are the names used in
  the example.
- The minimal example predates the fork declaration requirement and names
  none of pragueBlock, osakaBlock or amsterdamBlock.
- Note 6 refers to a "strong pinning warning" that is not marked as a
  warning anywhere; it means the pinning requirement in note 3.

No other wording changes.

* fix(params): order the fork listings consistently (#2770)

* fix(params): order the fork listings consistently

ActiveForks documents that the list it returns is sorted in ascending ASCII
order, but TIPEpochHalving was appended after TangerineWhistle, which sorts
later. Move the append to its alphabetical slot, between TIP2019 and
TIPIncreaseMasternodes.

The benchmark-only mirror activeForksLegacyMapSort never carried Gas2500x: the
production list gained the name in #2516 and the mirror was not updated with it.
The gap stayed invisible because BenchmarkActiveForks did not schedule the fork
either, so the benchmark would only have compared two different lists once some
config scheduled Gas2500x. Enumerate the name in the mirror and schedule the
fork in the benchmark, so both sides enumerate the same forks.

Add TestActiveForksLegacyMirrorMatchesActiveForks, which pins the mirror to the
production list. It compares the two implementations over a pair of configs that
between them activate every fork - scheduling the two Disable fork blocks turns
TIPXDCXMiner and TIPXDCXReceiver off and XDCxDisable on, so one config cannot
cover every name - and it fails when either list gains or loses an entry or when
the production order drifts from the ASCII order the mirror sorts into.

String and Description render the fork fields in a different order than
chainConfigForkOrderFields, the order CheckConfigForkOrder enforces. #2811
renumbered that order so DynamicGasLimit, TIPUpgradeReward and TIPUpgradePenalty
activate before Prague and Osaka, but the two renderings were not moved with it:
they kept Prague, Osaka and the Amsterdam line that #2756 added beside them ahead
of that trio. Move the six fields into fork order and pin them, together with
TIPEpochHalving that follows them, in TestChainConfigRenderingFollowsForkOrder,
which reads the expected order off chainConfigForkOrderFields, so the fields it
pins cannot drift out of fork order again.

The package comment in params/forks promised an order the enum does not hold: it
said the forks are ordered by XDC mainnet activation height with unscheduled
forks last, but Constantinople, Petersburg and Istanbul, none of which mainnet
scheduled, sit in the middle of the enum, ahead of TIPSigning at 3,000,000. Say
what the enum is instead - a naming registry for the fork labels with no
consensus meaning - and point at chainConfigForkOrderFields, together with
chainConfigForkOrderSpecialCaseRules, for the order that is enforced.

The ordering defects all predate this change: the ActiveForks list and its
misplaced append came together in #2326; the missing mirror entry arrived in
PR #2516, and the renderings were left behind by #2811. TIPEpochHalving is nil
on all five live networks and on all six dev and test configs, so none of the
reordering changes a chain's behaviour. What changes is the order of the fork
list in the config banner, which prints every fork field regardless of
scheduling, and, where the forks are set, the order of the String rendering and
of the activeForks array in the chain config view. The docs entry for activeForks
now states the ordering guarantee it relies on.

* fix(params): render the dao fork line after tip2019 in the config banner

chainConfigForkOrderFields puts TIP2019 (fork order 2) before DAOFork
(fork order 3), and String already renders them that way, but the
Description banner has listed DAO Fork and DAO Support ahead of TIP2019
since long before #2811. Move the two lines after TIP2019 so the banner
follows the same order as String, and extend
TestChainConfigRenderingFollowsForkOrder to the pair; the banner spells
DAOFork as "DAO Fork", so the test maps that single label for it.

With the last rendering divergence gone, the params/forks package comment
can state that both renderings follow chainConfigForkOrderFields.

* fix(params): reject chain configs that skip a fork their rules depend on #20169 (#2772)

Ref: geth #20169 (c476460cb2)
Refs #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, Amsterdam)
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 #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.

The scan also supersedes the OsakaBlock -> AmsterdamBlock special case that
#2756 added, together with the beforeRequired mechanism that only served it.
Both forks are standard slots, so the scan already rejects "OsakaBlock is unset,
but AmsterdamBlock is scheduled" with the wording that rule used, and the
rule's premise (the linear comparison only covers forks that are both set) no
longer describes the validation. Removing the rule, the beforeRequired field and
its branch, and the two comments that referenced it changes no behaviour: the
scan runs first and the error is identical. The ADR records why the rule could
go while its wording stays.

Two pre-existing gas2500x rejection cases only asserted the
ErrWrongForkSwitchOrder sentinel, which the sequence scan now shares; they
assert the full error string here. Each trigger-only slot carries a case of its
own, so every standard slot is pinned as a rejection source rather than only as
a slot the scan walks past. The field names the sequence scan compares and
reports are constants checked against both the fork block and the fork order
tables at init, so a rename in the generated table fails loudly instead of
silently disabling the cancellation-fee fallback or changing which pair
Petersburg is checked against.

* fix(params,genesis): declare constantinople/petersburg/istanbul blocks (#2777)

The built-in XDC network configs left ConstantinopleBlock,
PetersburgBlock and IstanbulBlock unset. At runtime
ChainConfig.IsPetersburg/IsIstanbul fall back to
TIPXDCXCancellationFeeBlock, so those two forks activated at the
cancellation-fee height (mainnet 38383838, testnet 23779191,
devnet 0), while IsConstantinople has no fallback and was never
true on any of the three chains.

Declare the three slots explicitly at those heights, following the
upstream convention of setting a skipped fork at its successor's
block (geth mainnet sets Constantinople and Petersburg both at
7280000), and mirror the values into the embedded
genesis/{mainnet,testnet,devnet}.json so the two startup paths stay
in lockstep. In the genesis files the trio keys sit next to
DenylistBlock, the height they share, rather than after
byzantiumBlock: those files group fields by activation height and
their key order is an append history that has never matched the Go
struct order.

Execution is unchanged: GatherForks (hence forkid) is identical
before and after, Istanbul rules were already active at these
heights through the fallback, and the newly-true
IsConstantinople only adds an ActiveForks entry.

Because the slots now carry values, the fork-order validation
moves them after TIPXDCXCancellationFeeBlock; without that, filling
them is rejected with "IstanbulBlock 38383838 > TIPSigningBlock
3000000". The rejection also names the field to move: both the
ordered scan and the special-case rules append the fix — declare
the later field at or after the earlier one, plus, when that field
is not mandatory for the config's shape, that dropping it works
too — instead of leaving the operator to work out which of the two
fields to move. The eight slots between Byzantium and the trio
(TIPSigningBlock..TIPXDCXCancellationFeeBlock, including DenylistBlock)
shift down by three so the ordering key stays dense at 1..34 and
the trio takes 16..18. The standard slots keep their relative
order (Constantinople 16 still sits before Berlin 20), so the
sequence scan reads the same sequence it did before. Test
fixtures that push the XDC forks into the future carry the trio
together for the same reason, and new tests pin the declared heights,
the unchanged fork id, and the configs the new position refuses. The
built-in Localnet config stays unfilled, because it is the hydration
source for custom chain configs.

Nodes running a stored config from before this change need no
action: the setup and load paths hydrate the stored config from
the built-in one before the compatibility check, so the newly
declared slots are backfilled rather than compared.

Reorder the JSON field-table rows so the physical order of the trio
follows the forkOrder key (the trio now physically follows
TIPXDCXCancellationFeeBlock); the generator sorts the fork-order table
by the key, so this changes no output: the trio stayed the last three
rows of the common table, which is the order the semantic config digest
reads. Row order is not free elsewhere - the common table keeps the JSON
row order, and chainConfigForkBlockFields preserves it into that digest,
so reordering its rows changes it. The built-in table is emitted as the
standard fields followed by the XDC-specific ones, each group in JSON row
order, so only a move inside a group changes it. The fork-order
table's own rows carry no semantics: they were already not fully aligned
with the key before this change and still are not
(TIPXDCXMinerDisableBlock and TIPXDCXReceiverDisableBlock still follow
the Cancun..Osaka rows), so the move is readability only. Pin the PR
description's own rejection example as a test case: the scan collides
with ConstantinopleBlock first, so that is the slot the error names.

Move the trio in the Fork enum and in the label map next to the
forks they share the 38383838 height with (Denylist through
TIPTRC21Fee), so the three declarations sit together. String() and
Description() move the trio the same way, so both renderings keep
following the fork order, and TestChainConfigRenderingFollowsForkOrder
now pins the trio together with the cancellation-fee anchor it sits
after. The enum values carry no meaning: the package is reached only
through String(), and no production code persists or compares a Fork;
TestGasTierForksAreOrdered pins Gas50x before Gas2500x, which this
move does not touch. The comment above the enum is left alone: it
already describes the order as a naming registry rather than as an
activation order.

Pin the one user-visible effect, ActiveForks gaining
"Constantinople" on the three built-in networks while Localnet
stays nil, so that entry cannot drift silently.

Update the statements this change makes stale: the IsIstanbul and
IsPetersburg backward-compatibility notes in params/config_forks.go
named the three networks as configs that leave the slot unset, and
now name Localnet instead. The ADR that records the sequence check
(docs/adr/0002-require-standard-fork-sequence.md) listed the three
among the networks leaving the trio empty in its migration note,
rested a rejected option on the same list, and recorded the
fallback-versus-declared-slot rejection as out of scope; the
reorder makes that rejection disappear in the direction the entry
described and appear in the other one, so the entry is updated
rather than dropped, and its config_forks.go line anchors follow
those two comments to their new lines.

Hydration of the three declared slots is a single decision: every slot the stored
config takes a position on must already agree with the anchor the built-in
networks declare, where a missing slot reads through the cancellation-fee
fallback and an explicit null is authoritative: it is neither hydrated nor
used to veto the others. No slot moves unless all of them agree, so a stored
schedule never gains a fork-ID boundary it did not have, and the batch is
re-validated as a whole and rolled back when the full startup check
(CheckConfigForkOrder) rejects it. Fork-order repair hints follow the same
rule: a slot is only offered as droppable after clearing it and re-validating
the result, so a diagnostic cannot recommend a change the next validation
rejects.

* fix(core,internal/ethapi): add fork validation in the state transition #35588 #35914 (#2781)

* fix(core,internal/ethapi): add fork validation in the state transition #35588

Messages built outside the transaction pool (eth_call, eth_createAccessList,
eth_estimateGas, debug_traceCall and eth_simulateV1) bypass the signer, so a
transaction type that is not enabled yet at that block was still accepted and
executed there.

The set-code case is reachable on mainnet, where EIP1559Block is set while
PragueBlock is unset: every post-EIP-1559 header is a pre-Prague header. Testnet
activates Prague at 87,633,000, so the same window exists before that block.
eth_simulateV1 accepted an authorization list on one of them, applied the
delegation, and handed back a type-4 transaction; the same chain then rejects
that transaction as "pool not yet in Prague", so the block it returns cannot be
replayed against the chain it was simulated for.

Reject an access list before EIP-1559 and a set-code authorization list before
Prague, both reporting ErrTxTypeNotSupported the way the transaction pool does.

ported from geth 2dfea58b921acd6bd17771a43871c7392bc23054 (#35588), which fixes
the same bypass: the normal execution path is guarded by the signer, but
eth_call and the state tests construct messages directly. The blob check added
by that commit has no counterpart here, because this fork has no blob
transaction type and Message carries no blob fields. Upstream receives the fork
rules as a preCheck argument; this port adopts that signature, with execute
resolving the rules before the call, so the check and the rest of the function
read the same rules instead of re-reading the chain config.

The access list check is gated on EIP-1559 rather than on the Berlin activation
the upstream commit uses. This fork schedules Berlin and EIP-1559 at different
blocks (mainnet 76,321,000 and 98,800,200), and its transaction pool rejects
every non-legacy type, access list included, until EIP-1559 is active. Gating on
Berlin would leave a 22,479,200-block window where eth_call still executes a
transaction the pool refuses; following the pool keeps the two layers in step.

eth_createAccessList needed one deviation. It attaches the access list its
tracer produced to the message, and that list is non-nil even when it is empty,
so before EIP-1559 every call, with or without a caller-supplied list, would
have been rejected. The computed list is now attached only once EIP-1559 is
active; a list supplied by the caller still reaches the message and is still
rejected before the fork. Upstream attaches it unconditionally and therefore
rejects the method before Berlin; the divergence keeps the method usable there,
which is the behaviour before this commit.

An empty JSON access list ("accessList": []) decodes to a non-nil empty slice,
which the state transition reads as a type-1 marker. ToMessage now drops it, so
the request behaves as if the list were omitted.

Verified on Linux amd64 with Go 1.25.14:
- make all, gofmt and goimports are clean; go test ./core ./internal/ethapi
  passes and golangci-lint --new-from-rev reports no new issues.
- TestPreCheckRejectsTransactionTypesBeforeTheirFork covers both types on both
  sides of their fork: removing a guard fails its rejection case, and making
  the check unconditional fails the accepted cases.
- TestApplyMessageRejectsAccessListBeforeEIP1559 covers the production call
  path, where execute resolves the rules.
- TestApplyMessageAcceptsAccessListAtEIP1559 covers the accepting side on the
  same production path.
- TestSimulateV1RejectsAuthorizationListBeforePrague covers the end-to-end case
  on top of the merged simulation guards: an eth_simulateV1 call carrying a
  valid authorization list and no gasPrice against a post-EIP-1559, pre-Prague
  header fails with "transaction type not supported: setcode tx" instead of
  returning a type-4 transaction.
- TestCreateAccessListBeforeEIP1559 and
  TestCreateAccessListRejectsCallerListBeforeEIP1559 cover the method before
  EIP-1559: the computed list is returned but not attached, so gasUsed leaves
  out the EIP-2930 intrinsic cost, while a caller-supplied list is still
  rejected.
- TestToMessageDropsEmptyAccessList covers the empty JSON list, which must not
  reach the message as a type-1 marker.

Ref: #35588

* fix(internal/ethapi): reject 1559 fee fields in calls before the fork #35914

eth_call, eth_estimateGas, eth_createAccessList, eth_simulateV1 and
debug_traceCall ignore maxFeePerGas and maxPriorityFeePerGas on a block whose
header has no base fee. ToMessage drops them in that case, so a call with a
2 gwei fee cap runs with GASPRICE 0, and eth_simulateV1 hands back a dynamic-fee
transaction that the pool then rejects as "pool not yet in EIP1559".

Reject the fields in CallDefaults when the base fee is nil, using the message
that setFeeDefaults already returns for the same case. A zero value counts and
gasPrice stays valid.

eth_createAccessList targets a block rather than the chain head, and the two can
sit on different sides of the fee fork. It now derives the fee defaults from the
target block's header (setDefaultsWithHeader) instead of the head; otherwise the
head's 1559 defaults would be filled in and the check above would reject a
historical call that never supplied them.

This ports geth 8a8efd3324579de83ffb2f272505de6ba0314ffb (#35914). The upstream
message names London; this fork schedules London and EIP-1559 at different
blocks (mainnet 76,321,000 and 98,800,200), so the wording follows setFeeDefaults
and names EIP-1559. The check still keys on the base fee rather than the fork
rules, so a baseFeePerGas block override before the fork is not rejected by
this check. ToMessage still uses the header's base fee, so that combination is
not a working call path (it yields nil fee fields); that mismatch predates
this change.

Two simulate tests paired the 1559 fee fields with a block that carries no base
fee: TestSimulateV1 passed them to a contract sender, and
TestSimulateV1BaseFeeNonValidationMode to its first block. Those calls now leave
the fields out; both tests exercise other behaviour and the fee fields were
silently ignored there before.

Verified on Linux amd64 with Go 1.25.14: make all, gofmt and goimports are
clean; go test ./internal/ethapi ./eth/tracers passes.

- TestCreateAccessListBeforeEIP1559WithoutFeeFields covers a head past EIP-1559
  with a target block before it and no fee fields in the request.

Ref: #35914

* fix(core): check the pre-existing Finalize error in GenerateChain (#2782)

GenerateChain dropped the error returned by consensus.Engine.Finalize, so
a failing finalization (for instance a reward hook error) was silently
discarded: every engine in this tree returns a nil block together with
the error, so the generator committed the failed block's state and handed
back a chain holding a nil block. With n == 1 that nil block reaches the
caller; with n > 1 the next iteration dereferences it in parent.Root().

In this fork Finalize returns (*types.Block, error), while its geth
counterpart returns nothing (geth removed the error return in #19301,
4c90efdf5; #34726 later removed FinalizeAndAssemble), so there is no
upstream fix to port. Surface the failure in the same style as the state
and trie write error handling right below, and keep the GenerateChain
signature unchanged.

Ref: #2605

* chore(contracts): regenerate the v1 contract bindings (#2784)

These twelve generated files were last written in January 2025, before the v1
abigen template was rebuilt by the #31379 backport and before the template fixes
that landed here with #2645 (geth #25574), #2628 (geth #35701) and #2631
(geth #32164). Regenerating now brings all of them in at once, which is why it
was held back until those three were merged.

The input is the ABI and bytecode already committed in these files, so the
payload is unchanged byte for byte and only the shape of the generated code
moves forward: XxxMetaData is added and XxxABI/XxxBin become deprecated
aliases, Deploy* and bind* use MetaData.GetAbi() instead of re-parsing the ABI
JSON on every call, library linking no longer substitutes into the shared
package-level XxxBin, the Parse<Event> helpers appear, and the Watch<Event>
loops skip a log whose signature does not match instead of ending the whole
subscription.

One API reduction: types with no bytecode, namely the abstract interfaces
ITRC21, AbstractXDCXListing, AbstractTokenTRC21, LAbstractRegistration,
LAbstractTokenTRC21 and LAbstractXDCXListing, no longer get XxxBin and
DeployXxx, because the template guards both on a non-empty bytecode input.
Nothing in the tree references them.

Regenerating means running the in-tree abigen against a combined-json document
rebuilt from the checked-in ABI and bytecode. Two details matter when doing so:
the combined-json "bin" field must hold bare hex, since the template prepends
the 0x itself, and XDCValidatorBin is the one literal in these files that has no
0x prefix.

Verified with make all and make quick-test (127 packages, no failures).

* fix(core/state,eth,internal/ethapi): make DeleteAddress removal visible in the session, close #2611 (#2783)

* fix(core/state): invalidate session cache in DeleteAddress (#2611)

DeleteAddress loaded the account into the live cache via getStateObject
before deleting it from the trie, and deleteStateObject only removed it
from the trie. The cached object therefore stayed readable for the rest
of the session: GetBalance, Exist and GetCode kept reporting the
pre-deletion account, while IntermediateRoot/Commit already reflected the
deletion, so in-session reads disagreed with the final state.

Drop the object from the live cache, record a deletion mutation and mark
the account destructed, the same way the self-destruct path does, so the
removal is visible to the rest of the session. The deletion mutation
replaces any pending update for the address, which would otherwise be
applied against the missing object. Marking the account destructed
additionally lets handleDestruction track the original account value as a
deletion at commit time.

The removal is journalled as well. DeleteAddress is a public API, and
dropping the object from the live set made a later journal revert read a
missing object: balanceChange, nonceChange, codeChange and storageChange
all dereference getStateObject, so reverting past the call panicked. The
new journal entry carries a copy of the object and of the block-scoped
bookkeeping the removal overwrites; reverting to a snapshot taken before the
call restores all of it. The account trie entry is deleted by the next
IntermediateRoot, so the revert does not have to write it back.

The only production caller, ApplyTIPSigningHardFork, runs before the
first transaction of the activation block. Both networks set
TIPSigningBlock to 3,000,000 and have long passed it; that block carries
no transactions on either network (mainnet and Apothem: txs=0,
gasUsed=0), it is not a reward checkpoint, transactions addressed to the
account are routed to ApplySignTransaction which only bumps the sender
nonce, and the non-EVM log helper only appends logs. No historical block
therefore observes or recreates the account, and the state root is
unchanged. A network that sets TIPSigningBlock in the future must
confirm its activation block contains no transaction that reads or
recreates the account.

The API has behaved this way since it was introduced in 7833a10c16
(2018). geth has no DeleteAddress and no corresponding fix to port; the
destruct-marking mirrors the in-repo self-destruct, Finalise and
handleDestruction machinery.

Fixes: #2611

* docs(eth,tracers,internal/ethapi): refresh TIPSigning DeleteAddress comments

These comments still claimed DeleteAddress removed the account from the trie only and left the cached state object readable in the same session, which is no longer true after the session-cache fix. Update them: the removal is now visible in-session, and the missing traceBlock/traceChain cases in #2591 are a deliberate trade-off rather than an impossibility.

* docs(core/state): correct the stale StateDB.Copy snapshot caveat

The doc said a snapshot of the copied state cannot be applied to the copy, but
journal.copy copies validRevisions and nextRevisionId, so a snapshot taken
before the copy does revert on the copy. TestDeleteAddressCopyRevert relies on
that behaviour.

The caveat predates this branch (present at 1d7ebe2047:core/state/statedb.go:811)
and go-ethereum master 943517bd62 carries the same wording with the same
implementation, so it is corrected in its own commit instead of being folded
into the DeleteAddress change.

* fix(accounts/abi): chain nonces when deploying library dependencies #35888 (#2786)

When a generated v1 binding deploys a contract that links a library, the
library is deployed first and the main contract then asks the txpool for
the next nonce. The pool updates its pending nonce asynchronously, so the
main contract can be handed the library's nonce and fail with
"replacement transaction underpriced". WaitAccepted does not help, because
it returns as soon as the transaction sits in the pool, before the nonce
is updated. Set the nonce to the library transaction's nonce plus one
instead of asking the pool again, and copy the opts first so the caller's
are left untouched.

The upstream patch needs two adaptations on this fork:

- The fork template guards the acceptance wait with !auth.NoSend, so the
  nonce bump sits inside that guard: a transaction that is never submitted
  cannot move the pool, and forcing a nonce would change the semantics of
  NoSend deployments.
- The if/end guards around the opts copy carry whitespace-trim markers, so
  a binding without library dependencies generates byte-for-byte the same
  code as before. Without them every no-library Deploy function would gain
  a blank line and the checked-in contracts/ bindings would go stale the
  moment this lands; regenerating all twelve was re-run and confirmed to
  stay a byte-for-byte no-op.

Library linking keeps substituting into the function-local bytecode copy
from #2631 rather than the package-level XxxBin, so the two changes
compose.

TestBindLibraryDeploymentWait now also asserts the opts copy and the
redirect to it, the nonce bump, both positions before the library
deployment and the bump, the bump's placement inside the NoSend guard and
after the acceptance wait completes, and the absence of both in a binding
without library dependencies. With several library dependencies it also
checks that the opts are copied once and that the nonce advances after
every library transaction.

Ref: #35888

Verified with make all and make quick-test (no failures).

* perf(core, XDCx): reuse the cached TRC21 ABI instead of re-parsing it per call (#2787)

Both helpers parsed the TRC21 ABI out of its JSON string on every call: core's GetTokenAbi ran for every apply-class transaction while the XDCX receiver fork was active (the pool-admission and miner callers pass a nil block number, which the validators substitute with the current header number before the gate, so on an activated fork those paths reach the ABI exactly like the import path), and XDCx's GetTokenAbi ran on every token decimal cache miss. The regenerated binding merged as #2784 now exposes the same ABI through TRC21MetaData.GetAbi(), which parses once per process and returns the cached *abi.ABI, so consume it directly at the three call sites and drop the two thin wrappers, which also removes the last use of the strings package in both files.

No behaviour change: MetaData.GetAbi() returns the same ABI defined by the exported string, and the cached value is only read. The caching contract itself is pinned by accounts/abi/bind tests, and the XDCx call site is pinned by TestGetTokenDecimalUnpacksWithCachedTRC21ABI…
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.

on contract subscription error, the subscription is dropped

3 participants