Skip to content

chore: merge dev-upgrade into main - #2903

Merged
benjamin202410 merged 57 commits into
mainfrom
dev-upgrade
Oct 27, 2026
Merged

benjamin202410 merged 57 commits into
mainfrom
dev-upgrade

Conversation

@benjamin202410

Copy link
Copy Markdown
Collaborator

Proposed changes

Merge dev-upgrade into main: 50 commits since the last merge in #2810.

Highlights:

Types of changes

What types of changes does your code introduce to XDC network?
Put an ✅ in the boxes that apply

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

Impacted Components

Which parts of the codebase does this PR touch?
Put an ✅ in the boxes that apply

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

Checklist

Put an ✅ in the boxes once you have confirmed below actions (or provide reasons on not doing so) that

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

🤖 Generated with Claude Code

gzliudan and others added 30 commits October 16, 2026 15:33
…ata (#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 4a305af) and has no fix for it, so this change is
made independently without referencing an upstream PR.
…che (#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

* 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
f8f9bc5. 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>
#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>
ported from go-ethereum 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.
…n (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 ethereum#18087 port (#946, b9a6c8c): 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 ethereum#18087
(434dd5b) 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: ac2f09f (#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.
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 (ethereum#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.
…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.
…reum#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 ethereum#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: ethereum#21414
…rds (#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
…ereum#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: ethereum#20053
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.
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
…reum#33832 ethereum#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 ethereum#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: ethereum#33832 ethereum#35217
#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

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.
… on ethereum#20169 (#2772)

Ref: geth ethereum#20169 (c476460)
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 ethereum#21473 states the intent as "fork ordering requirements which state
that Petersburg fork be set if Constantinople fork is set".
TIPXDCXCancellationFeeBlock stays the effective activation of both Istanbul and
Petersburg, mirroring IsIstanbul and IsPetersburg, while TIPXDCXMinerDisableBlock
deliberately does not count.

That scan reports a gap only when a later slot carries a value, so it cannot see
a schedule that stops at ConstantinopleBlock, where IsConstantinople is true
while IsPetersburg stays false and the net-metered EIP-1283 SSTORE rule runs
with no fork declaring it. chainConfigForkSequencePetersburgPair checks that
pair on its own; declaring PetersburgBlock or activating it through
TIPXDCXCancellationFeeBlock keeps the config valid.

Post-Shanghai, upstream allows a skipped optional fork; this repository is
stricter because its instruction sets and precompile bags are selected by flags
rather than by the highest configured fork. See
docs/adr/0002-require-standard-fork-sequence.md.

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.
#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.
…ethereum#35588 ethereum#35914 (#2781)

* fix(core,internal/ethapi): add fork validation in the state transition ethereum#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 2dfea58 (ethereum#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: ethereum#35588

* fix(internal/ethapi): reject 1559 fee fields in calls before the fork ethereum#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 8a8efd3 (ethereum#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: ethereum#35914
)

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 ethereum#19301,
4c90efd; ethereum#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
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).
…le 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 7833a10
(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 1d7ebe2:core/state/statedb.go:811)
and go-ethereum master 943517b carries the same wording with the same
implementation, so it is corrected in its own commit instead of being folded
into the DeleteAddress change.
…thereum#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: ethereum#35888

Verified with make all and make quick-test (no failures).
… 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, which fails when the call site packs with an ABI that lacks the TRC21 decimals method.

It also fixes a stale doc comment on TestMetaDataGetAbiCachesParse in accounts/abi/bind: the comment said the cached accessor had no caller, which held when #2722 wrote it (no GetAbi() call site existed under contracts/ at that commit) but stopped holding once #2784 regenerated the v1 bindings.

Ref: #2785
…lose #2788 (#2789)

* fix(accounts/abi/abigen): resolve v1 identifiers in a stable order

The v1 generator walked evmABI.Methods and evmABI.Events directly, and both are
maps. Two methods can normalize to the same Go identifier - the
NumericMethodName fixture has _1test and __1test, which both become M1test - and
which of them keeps the name and which one is given a suffix was decided by
abi.ResolveNameConflict in whatever order the map yielded. The same ABI
therefore generated two different bindings: in one run M1test was 0xffa02795
(_1test) and M1test0 was 0xd02767c7 (__1test), in the next the two swapped.
Nine regenerations produced two distinct files.

Walk the sorted keys instead, for the events as well, since they claim
identifiers the same way. The order of the generated methods never depended on
this - text/template sorts map keys when ranging over one - but two other things
did: the competing identifier claims, and the names of anonymous tuple types,
which are numbered Struct%d in the order the walk first meets them. The
lexicographically first original name now keeps the unsuffixed identifier, which
is the choice abigen v2 already makes:
testdata/v2/numericmethodname.go.txt has E1test for 0xffa02795 and E1test0 for
0xd02767c7.

Upstream carries the same unordered loop (accounts/abi/abigen/bind.go:129), so
this is a fork-side fix rather than a port. Reported as #2788.

Verified with make all, make quick-test (no failures), and by regenerating the
v1 golden corpus of the next commit twenty times in a row without a difference.

* test(accounts/abi/abigen): pin the v1 bindings against golden output

TestBindings generates the same contracts, but it only compiles and runs them,
so a v1 template change that still produces working code goes unnoticed: the
template can drop a caching layer, emit a different helper or stop generating a
deprecation alias, and every test stays green. The checked-in bindings under
contracts/ shipped for eight months in exactly that state (#2652).

Add the output of every case in bindTests as testdata/v1/<name>.go.txt and
compare the generator against it, one file per case. Regenerate with
WRITE_TEST_FILES=1, the switch the v2 corpus already uses.

A single comparison only catches an unstable walk when the map happens to yield
the other order, so TestBindingV1IdentifierOrder generates the colliding cases
64 times as well. It also pins which original name keeps the unsuffixed method
and event identifier, and that anonymous tuple types keep their Struct0/Struct1
names, which the golden files only cover with a single struct.

go-ethereum keeps golden output for v2 bindings only, so there is nothing to
port; the mechanism follows testdata/v2 and TestBindingV2ConvertedV1Tests.

Checked that the corpus notices what the existing tests cannot: with the
template's bind{Type} parse point reverted to
abi.JSON(strings.NewReader(...)) - the divergence reported as #2620 - the new
test fails while TestBindings still passes.

Verified with make all, make quick-test (no failures), twenty consecutive runs of
the new test without a difference, and three regenerations producing
byte-identical output.
)

Nothing in the repository imports XDPoSChain/bmt: a grep over the whole
tree finds no importer outside the package itself, and the Makefile, the
CI test selection and the docs never reference it. Upstream go-ethereum
deleted the top-level bmt/ in e187711 (2018-06-20, "swarm: network
rewrite merge") and the swarm/bmt copy left the tree with geth ethereum#19661
(2019), so no maintained upstream revision is left to compare against.

Removing the package also drops the three data races reported in #2792
(run() vs finalise() walking the same tree nodes, plus the write-only
ha.hash field) from a -race sweep, so the package needs no exclusion
there.

Closes #2792
Closes #2796

Ref: ethereum#17041
…close #2794 (#2799)

The fairness tests read genIter.index with atomic.LoadUint32 in Next,
but Close assigned it with a plain write. When FairMix.Close ran while
a reader was still iterating, TestFairMix and TestFairMixNextFromAll
reported a data race under -race. Write the field atomically, matching
the upstream fix in ethereum#23434.

The dial scheduler also stored the connection flags of every added
peer with a plain read, d.peers[id] = c.flags, while Server.run sets
the trusted flag on the same word through a sync/atomic CAS, so
go test -race ./p2p/ reported a race between p2p/dial.go and
p2p/server.go. The map is used purely as a set -- assignment, delete,
len and membership check, never the stored value -- so store struct{}{}
instead and drop the racy access, again matching the upstream fix.

Ref: ethereum#23434
 #2791 (#2800)

Port the internal/testlog half of upstream ethereum#29301 ("eth/protocols/snap,
internal/testlog: fix dataraces"): bufHandler gains a sync.Mutex which
Handle, WithAttrs and logger.flush now hold while touching the record
buffer, and the struct literals are switched to named fields. The file
was identical to the upstream revision before that commit except for
the import paths, so the port needs no local adaptation.

The snap sync_test.go half of that commit is not applicable: this tree
has no eth/protocols/snap package.

Ref: ethereum#29301

Local verification on xdc/dev-upgrade tip 3095c15:
- go test -race -short -count=1 -run 'TestServerListen|TestServerDial|
  TestServerPeerLimits|TestServerSetupConn' ./p2p/ reported DATA RACE
  before the change; the testlog races are gone after it.
- make quick-test and make all pass; gofmt/goimports are clean.

The branch has since been rebased onto 3777382; make all still passes
there, and now that #2794 has also landed go test -race -short -count=1
./p2p/ is green end to end (2/2 runs).
The traceTx watcher only stopped the tracer when the per-transaction deadline expired, so a caller that had already gone away kept the trace running until its own timeout. Stop on any reason the deadline context is done, and mark the trace as finished before the deferred cancel() runs, so a watcher woken by that cancel() never stops a tracer whose execution already finished.

The new tests trace a contract that only ends when its gas runs out: with the default, unlimited struct logger every executed opcode is kept, which is tens of gigabytes for 500M gas and enough to send the host into the OOM killer. Bound the collected logs in those tests; they never inspect them.

Ref: #2640
… (#2802)

The handler tests increment their counters with atomic.AddUint32 from
the Bfter.loop goroutines, but the assertions read them with plain
loads, so the test goroutine races with the broadcast handlers it just
triggered: go test -race reports four data races (bft_handler_test.go:92,
:236, :288 and :363) and fails TestSequentialVotes, TestTimeoutHandler,
TestSyncInfoHandler and TestTooFarTimeout. Which of them fail depends on
when the race reports land, so the failing set varies between runs.

Read every counter with atomic.LoadUint32 in the assertions, both in
the conditions and in the t.Fatalf arguments; the increment sites are
left untouched.

go-ethereum never had a bft package (git ls-tree HEAD eth/ lists no bft
entry, and a case-insensitive grep over the master tree finds nothing),
so there is nothing upstream to port. The code entered this fork with
a1b77f3 (XDPoSChain PR #13, 2021-11-06), whose eth/bfter/bft_test.go
carries the same plain-read pattern.

Closes #2793
gzliudan and others added 18 commits October 20, 2026 19:41
#2732 (#2807)

The two test backends in this file replayed the TIPSigning and Multicall3
block level pre-execution hooks but not the third one production applies
before the first transaction, so the pre-state they handed to a tracer was
missing the EIP-2935 parent hash history write block processing performs.
params.TestChainConfig activates Prague at block zero, so every fixture in
this file produced a pre-state short of that write.

Mirror the production order in both backends: build the EVM first, then
apply the three hooks, then replay the transactions. Constructing the EVM
ahead of the hooks also puts the empty block early return on the production
shape, which eth/state_accessor.go already has, so an empty block now
carries the history write the way it does in production.

No production code changes. Upstream go-ethereum has no corresponding fix
to port: its tracer fixtures rebuild no block level pre-execution at all.
…, close #2738 (#2813)

* fix(cmd/utils): report a malformed block at its position ethereum#27213, close #2738

The CLI importer drops the exporting node's genesis block while decoding, but
counted it only after the skip, so n held the number of blocks kept rather than
the position in the input stream. The message that names a parse error read n, so
a malformed block came out one position early: a file cut short behind the
genesis block reported "at block 0" instead of "at block 1".

Count the block before the skip, which is what the RPC importer has done since

The other message that read n, "invalid block %d", no longer reads it: it names
the first block InsertChain says did not make it (upstream go-ethereum ethereum#27213),
so moving the count cannot shift it, and a batch that fails in the middle blames
that block instead of the end of the batch or a position past the file.

Upstream go-ethereum has no corresponding fix to port for the counting order: its
importer counts after the same skip, and no commit there touches that order.
Its invalid block message already reads the insert index, which is the shape this
commit copies (upstream: geth ethereum#27213 for that part).

Local verification: make all, gofmt and goimports, go run build/ci.go lint
./cmd/utils/..., and go test ./cmd/utils/. TestImportChainReportsTheFailingBlockPosition
fails with the counting moved back, reporting "at block 0";
TestImportChainReportsTheBlockTheInsertFailedOn fails against the n-based invalid
block message, reporting block 4 instead of 2.

Ref: ethereum#27213

* fix(core): keep the nil database error out of the merkle root message

ValidateState renders statedb.Error() through %w unconditionally, so the common
merkle root mismatch - execution completed, no database error recorded - came out
as "dberr: %!w(<nil>)". Add the dberr segment only when a database failure was
recorded; the message without it is what the mismatch always meant.

The non-nil path is unchanged. Upstream go-ethereum carries the same unconditional
%w (core/block_validator.go:201, introduced by ethereum#26870) and has no fix to port, so
this is a deliberate divergence; the cmd/utils importer test that pins the failing
block now also rejects a nil formatting verb in the message.

Local verification: make all, gofmt and goimports, go run build/ci.go lint
./core/... ./cmd/utils/..., go test ./core/ ./cmd/utils/ ./eth/, and
TestImportChainReportsTheBlockTheInsertFailedOn fails with the condition removed,
reporting the %!w(<nil>) text.
…lose #2735 (#2812)

eth_simulateV1 applies overrides before the block level state changes, so an
override of a field those changes write is undone or trips the consensus panic.
Refuse it on Multicall3, the EIP-2935 history contract and the legacy block
signers account at their Prague and TIPSigning blocks, and for a move of an
active precompile onto the history contract; a move from an account that is not
one cannot shadow the contract, and override.Apply rejects it before the guard
would need to. Fields no change writes stay overridable (#2711 covers
Multicall3), and a refusal names the lowest written slot and the lowest moving
account, so the response does not depend on map iteration.

Applying the overrides later would undo that guard and move the panic to the
next block, postponing it instead of removing it. Upstream has nothing to port:
geth neither installs code nor panics. The guard sees the simulated block
alone; #2855 tracks the rest.
ethereum#34812, close #2734 (#2809)

* refactor(core,eth,internal/ethapi,miner): consolidate block level hooks ethereum#34812, close #2734

Block processing and every block level replay apply the same three hooks in
the same order before the first transaction: ApplyTIPSigningHardFork and
ApplyMulticall3HardFork mutate the statedb, then ProcessParentBlockHash
writes the parent block hash history through the EVM. The sequence was
copy-pasted at nine production points (core/state_processor.go twice,
core/chain_makers.go, miner/worker.go, eth/state_accessor.go,
eth/tracers/api.go three times, internal/ethapi/simulate.go) and mirrored at
four test points (eth/tracers/api_test.go twice,
eth/tracers/api_tipsigning_activation_test.go,
eth/tracers/api_intermediate_roots_test.go). Missing one when adding or
reordering a block level state change desynchronises the pre-state a tracer,
eth_simulateV1 or the miner works against, and the failure mode is a wrong
intermediate root or a wrong trace rather than a compile error.

Add core.ApplyBlockLevelHooks, which applies the sequence in the canonical
order, and call it from all thirteen sites. The helper takes only the parent
hash and the EVM: the caller owns the EVM because the sites build it for
their own use (block processing reuses it for the transactions, the miner for
the work task) and only some of them need the extra state a miner or a tracer
attaches to it, and the activation config and the block number are read from
that same EVM. The three hooks therefore read their gate from that one EVM
instead of from two parameter lists plus the EVM context; parentHash is the
one input vm.BlockContext does not carry. Every write goes through
evm.StateDB, so a caller cannot hand the helper a state the transactions do
not run against.

The DAO fork is deliberately outside the set: misc.ApplyDAOHardFork stays
open coded at the call sites that process or generate blocks, the way
upstream core.PreExecution leaves it out (ethereum#34812).
Moving it in would apply it on the replay paths for the first time.

Behavior changes beyond moving code:

- parentState is now copied before all three hooks. The baseline copied it
  after ApplyTIPSigningHardFork and ApplyMulticall3HardFork and before
  ProcessParentBlockHash, while the miner takes work.parentState in
  makeCurrent before running any of them, so the import path now matches the
  miner. The helper applies the three hooks in one call, so the baseline's
  split cannot be reproduced without applying the hooks twice: the copy has
  to move as a whole, which is why it lands with the consolidation instead of
  in a commit of its own. HookReward only reaches masternode contract slots
  (GetCandidatesOwnerBySigner, GetVoters and GetVoterCap in
  contracts.GetRewardBalancesRate), none of which the hooks write, and the
  state root comes from the statedb, so no reward or root changes. At
  DAOForkBlock the two snapshots still differ by the DAO balances, because
  Process copies after misc.ApplyDAOHardFork while the miner copies before
  it; HookReward reads no balances, so the reward output still agrees. A
  future HookReward read outside those slots has to keep both snapshots in
  step.
- The Multicall3 install now writes through evm.StateDB. Block import wraps
  the state in a tracing wrapper only when vm.Config.Tracer is set, which is
  the --vmtrace path, so that path reports the account creation (OnCodeChange
  /OnNonceChange) where the baseline reported nothing. The state root is
  unchanged. debug_trace* hands the hooks the plain state and is unaffected.
  eth_simulateV1 hands them the wrapped state too, but its tracer carries call
  and log hooks only, no state-change hooks, so it is unaffected as well.
- core/state_processor.go moves the EVM construction above the hooks at both
  sites. In ProcessBlockNoValidator the first stop check now precedes the
  parentState copy, the EVM construction and the hooks, so a stop raised
  before the call runs none of the three hooks (the DAO fork above still ran).
  A stop raised while the hooks run only reaches the second check, which now
  sits below them, so that calculation runs all three where the baseline ran
  the two statedb mutations and skipped the EIP-2935 write. The state is
  discarded on either path. In Process, InitSignerInTransactions,
  balanceUpdated and totalFeeUsed also move below the hooks;
  InitSignerInTransactions only makes and caches signers, and the other two
  are initialisers, so none of the three touches the state.
- core/chain_makers.go and eth/tracers/api.go (traceChain) build the EVM
  unconditionally now, the way the geth equivalent of those sites does for
  core.PreExecution; outside Prague the call is a no-op.
- The two eth/tracers/api_test.go backends keep the EVM construction above
  their empty block early return, the shape PR #2807 gave them, and now run
  the sequence through the helper instead of the open coded calls.

TestApplyBlockLevelHooksReportsInstallToTheTracer and
TestProcessReportsMulticall3InstallToTheTracer pin the install reaching the
tracer through the EVM state, the second one through the production Process
call site. TestApplyMulticall3HardForkReportsInstallToTheTracer keeps
covering the hook in isolation. TestProcessRunsBlockLevelHooksBeforeTheFirstTransaction
pins that the hooks run ahead of the first transaction, so the transaction
already sees the install and the history write.
TestProcessSnapshotsParentStateBeforeBlockLevelHooks pins the reward snapshot
on the import path, and
TestProcessBlockNoValidatorSnapshotsParentStateBeforeBlockLevelHooks pins the
same snapshot on the validator-free path, plus that a stop raised before the
call returns ahead of the snapshot and the hooks.

Closes #2734
Ref: ethereum#34812 (core.PreExecution consolidates the upstream
pre-execution system calls; it is not a straight port: ApplyTIPSigningHardFork
and ApplyMulticall3HardFork are statedb mutations specific to this chain, and
the gate here comes from the EVM while upstream passes it as arguments)

Verification: gofmt and goimports clean, make all, golangci-lint
--new-from-rev=f285bf505b (0 issues), go vet ./core/..., go test -count=1
./core/... . make tidy and make generate are not run: no dependency and no
generated file changes.

* fix(core): hand GenerateChain's Finalize the parent snapshot

GenerateChain copied the state inside the Finalize call, after the block's
transactions had run through BlockGen.AddTx and after the block level
hooks, and handed that copy to Finalize as parentState. Process and
ProcessBlockNoValidator take the snapshot before the hooks, and the miner
takes work.parentState in makeCurrent, so a generated block whose
transactions write the masternode slots reached HookReward with a snapshot
block processing never uses.

Take the snapshot before the hooks and before the transactions, the way block processing
does. The state root is unaffected: it comes from the statedb,
not from the snapshot. The output only differs when a generated block is a
reward block and its transactions write the slots
contracts.GetRewardBalancesRate reads; GenerateChain and the consumers built
on it (ethclient/simulated) now agree with block processing there.

TestGenerateChainSnapshotsParentStateBeforeTransactions pins the transaction
half: a generated block that transfers to an account Finalize watches must
not see the transfer in parentState.
TestGenerateChainSnapshotsParentStateBeforeBlockLevelHooks pins the other
half: the snapshot predates the block level hooks, so it does not carry the
Multicall3 install the processed state has.

Upstream has no counterpart: geth's consensus.Finalize does not take a parent
state, so there is nothing to port.

Verification: gofmt and goimports clean, make all, go vet ./core/...,
golangci-lint --new-from-rev=f285bf505b (0 issues), go test -count=1 ./core
./ethclient/simulated.

* docs(eth/tracers): point the legacy traceBlockParallel precondition at ApplyBlockLevelHooks

The comment predates the helper: it came in with #2266 and tells callers to apply "block-level pre-execution system calls", the vocabulary the GLOSSARY now avoids, leaving each caller to rediscover the sequence. Name core.ApplyBlockLevelHooks instead, the single entry point every block level replay uses.

No code change: comment only.
* refactor(consensus/tests): remove unused v2 helper copies

GetSnapshotSigner and GetCandidateFromCurrentSmartContract in
consensus/tests/engine_v2_tests/helper.go have no callers: the only
references in the tree are the v1 helpers of the same name, which are
live and untouched. Their dedicated types signersList and masterNodes
were used by nothing else, so they go with them, together with acc4Key
and acc5Key, two package-level keys that were never referenced anywhere
in the package.

Verification: make all, go vet ./... (compiles all _test.go), make quick-test.

* refactor(consensus/XDPoS): drop unused nm slice in UpdateMasternodes

The slice was filled inside UpdateMasternodes and never read afterwards.
Because it is only appended to, the compiler does not flag it. No
behaviour change.

Verification: make all, go vet ./..., make quick-test.

* refactor(consensus,core/types,ethstats): remove dead forensics proof chain

The forensics reporting chain has been unreachable since #1517 (1a2c8ee)
commented the real implementations out and replaced them with empty shells,
and every test in consensus/tests/engine_v2_tests/forensics_test.go that
asserted on its events carried t.Skip("Skipping this test for now as we
disable forensics"). What is left of the reporting path therefore never runs
in production:

- SendForensicProof, FindAncestorBlockHash, getQcSignerAddresses,
  findQCsInSameRound, generateForensicsId, reverse,
  SendVoteEquivocationProof, generateVoteEquivocationId and
  GetVoteSignerAddresses had no caller outside the commented-out blocks, so
  the imports they were the only users of go as well.
- The only senders of the forensics feed were those two functions, and both
  were themselves called only from the /* ... */ blocks, so the feed never
  fired: Forensics.SubscribeForensicsEvent, the subscription scope and the
  feed go as well, together with the XDPoS facade that forwarded to them.
- ethstats subscribed to that feed and reported proofs it never received.
  Drop the subscription, the forwarding goroutine and reportForensics.
- The five types in core/types/forensics.go (ForensicsInfo, ForensicsContent,
  VoteEquivocationContent, ForensicProof, ForensicsEvent) had no user left
  once the producers and the consumer were gone.
- forensics_test.go loses its five event-asserting tests and keeps the three
  that only exercise the entry points that stay: VoteHandler,
  SetCommittedQCs and ForensicsMonitoring. engine_v2/forensics_test.go is
  deleted whole: its single test covered findQCsInSameRound, and
  SignHashByPK / RandStringBytes / getSignerAndSignFn had no reference
  anywhere else in the package.
- The commented-out implementations are not kept as code: they cannot
  compile again without the helpers removed above, so they are trimmed to
  design-note doc comments on the three shells - intent, flow-diagram
  links, and what a re-enable needs - with the full versions left to git
  history.

Kept on purpose: the Forensics type with HighestCommittedQCs,
SetCommittedQCs and ForensicsMonitoring, and the three shells that are
still called from the live path (ProcessForensics from ForensicsMonitoring,
ProcessVoteEquivocation and DetectEquivocationInVotePool from vote.go).

Breaking change: the exported SendForensicProof, FindAncestorBlockHash,
SendVoteEquivocationProof and GetVoteSignerAddresses are gone, as are
Forensics.SubscribeForensicsEvent, XDPoS.SubscribeForensicsEvent and the
five core/types forensics types. No caller inside this repository is
affected.

Verification: gofmt, goimports, make all, go build ./..., go vet ./...,
go test on ethstats, core/types, consensus/XDPoS and engine_v2_tests (ok).

* test(consensus/tests): re-enable the retained forensics tests (pre-existing)

The three tests that survived the dead forensics removal were still gated by
"Skipping this test for now as we disable forensics", a line added in #663
long before #1517 disabled the reporting path. They only exercise the entry
points that stay - VoteHandler, SetCommittedQCs and ForensicsMonitoring - and
all three pass once the skip is removed (5.23s / 0.16s / 0.18s), so the
message no longer describes anything real.

No production code change: the gate predates this branch, which is why this
lands as its own commit rather than folded into the removal.

Verification: make all, gofmt, go test -count=1
./consensus/tests/engine_v2_tests/ (ok).
)

* refactor(core,XDCx,XDCxlending,miner,consensus): remove dead code and mgo dependency

The matching-result chain lost its consumers when the mongodb support
removal (#1679) dropped the code that read the results back, so the data
was produced and never consumed:

- BlockChain.AddMatchingResult / AddLendingResult / AddFinalizedTrades had
  no callers, and the five caches they wrote, their initialisation, and the
  crypto import used only by them were never read.
- The local tradingResult / lendingResult maps built in ValidateTradingOrder
  and ValidateLendingOrder had no readers either.
- The trades themselves were never read: on the XDCx side they were a
  []map[string]string returned by CommitOrder/ApplyOrder and only packed into
  tradingstate.MatchingResult by ProcessOrderPending, which the miner then
  discarded, while on the lending side ApplyOrder returned
  []*lendingstate.LendingTrade directly. Either way the container reached
  callers that dropped it, so the trades slice/field no longer crosses the
  API; the cache-key helpers go with the types.
- The tradeRecord map in XDCx/order_processor.go existed only to fill that
  discarded container, and tradingstate.Trade (with its BSON helpers) was never
  instantiated anywhere, so tradingstate/trade.go is deleted whole.

The remaining BSON faces are dead in the same way: the GetBSON/SetBSON
methods and their helper structs on OrderItem, EpochPriceItem, LendingItem
and LendingTrade were only reachable through mgo, and state storage uses
RLP, so there is no trigger path at all (no direct .GetBSON()/.SetBSON()
call, no bson.Getter/Setter assertion, and no mgo API call anywhere in the
tree). SignatureRecord and the two zero-caller ComputeHash methods go with
them; LendingTrade.ComputeHash stays - it is called from the order
processor. globalsign/mgo loses its last import, so go mod tidy removes it
from go.mod/go.sum.

Every state-mutating settle/match path is kept: SubAmountOrderItem,
SetLastPrice, SetMediumPrice, InsertOrderItem, CancelOrder, DoSettleBalance
and the LendingTrade object written through InsertTradingItem. The rejects
path is kept too, because it is still consumed by the reject logging inside
ProcessOrderPending (XDCx/XDCx.go, XDCxlending/XDCxlending.go); the two
rejects returned in core/block_validator.go were always discarded there.

A review follow-up folded into this commit removes the rest of what only
existed to serve the dropped containers: the XDCx.XDCX.sdkNode field
(unread since #1679 removed SyncDataToSDKNode, its only writer), the
bson:"..." struct tags on LendingItem / LendingTrade (their json:"..."
tags stay - that is the live serialization contract),
and three return values whose results every caller discarded:
getTradeQuantity loses its *SettleBalance result (the DoSettleBalance call
inside it is untouched), and Lending.ProcessTopUp / Lending.ProcessRepay
lose their *LendingTrade result (the object is still built and written to
state inside the functions).

EpochPriceItem itself goes with those decoders: after its BSON methods and
ComputeHash are gone, the type has no reference left in the tree (it was
already unconstructed on dev-upgrade), so its file is deleted whole, and
ToBigInt - called only from the removed SetBSON decoders - goes with it.

Breaking change: this narrows the exported surface. The signatures below
lose their first (trades) return value, so implementations of the two
interfaces, and callers of these methods, outside this tree stop compiling:

- consensus/XDPoS/utils: TradingService.ApplyOrder, LendingService.ApplyOrder
- XDCx: XDCX.CommitOrder, XDCX.ApplyOrder, XDCX.ProcessOrderPending
- XDCxlending: Lending.CommitOrder, Lending.ApplyOrder,
  Lending.ProcessOrderPending

Two more exported methods lose their *lendingstate.LendingTrade result;
for these the LendingTrade object is still built and written to state
inside the function, only the returned handle is gone:

- XDCxlending: Lending.ProcessTopUp, Lending.ProcessRepay

(Lending.ProcessTopUpLendingTrade / Lending.ProcessRepayLendingTrade keep
their signatures - the liquidation path still consumes their results.)

Exported symbols deleted: tradingstate.MatchingResult,
tradingstate.GetMatchingResultCacheKey, tradingstate.EpochPriceItem (file
deleted whole), tradingstate.ToBigInt, lendingstate.MatchingResult,
lendingstate.GetLendingCacheKey, lendingstate.ToBigInt, tradingstate.Trade
together with
the trade-record constants of tradingstate/trade.go (file deleted whole),
the GetBSON/SetBSON pair and BSON helper structs of OrderItem /
EpochPriceItem / LendingItem / LendingTrade, SignatureRecord,
EpochPriceItem.ComputeHash, LendingItem.ComputeHash,
BlockChain.AddMatchingResult / AddLendingResult / AddFinalizedTrades.

Verification: make all, go build ./..., go vet ./... (compiles all _test.go),
make quick-test (-short -failfast), make tidy.

* refactor(XDCx): drop unused UpdateOrderCache

UpdateOrderCache, introduced by b5abbfe, has no callers in the tree:
on the dev-upgrade baseline it is already only the definition, and the
orderCache field it writes is never read anywhere. tradingstate.
OrderCacheLimit and tradingstate.OrderHistoryItem exist only to size and
type that cache, and GetOrderHistoryKey is called only from inside
UpdateOrderCache, so all four go with it. The lru import stays -
tokenDecimalCache still uses it.

Verification: make all, go build ./..., go vet ./... (compiles all _test.go),
make quick-test.

* refactor(XDCx,XDCxlending): drop unused math and relayer helpers

These helpers had no callers before this branch either: whole-program
reachability (golang.org/x/tools/cmd/deadcode) and a reference count over
all .go files, tests included, both leave only the declaration.

- XDCx/tradingstate: Mul/Div/Add/Neg/Exp/Max wrappers, EmptyOrderList and
  EmptyExchangeOnject (nothing reads them; state code uses big.Int methods
  and constructs its own empty values)
- XDCxlending/lendingstate: the same wrappers, WeiToEther and the token-list
  helpers superseded by tradingstate's copies (lendingcontract.go queries
  tradingstate directly)

Intentional survivors: EtherToWei and GetExRelayerFee are still used by
lendingstate tests, lendingstate.DecodeFinalizedResult has a live call site
in core/block_validator.go, and Sub/CloneBigInt stay in use.
… bson remnants (#2814)

The MongoDB backend of the XDCx database was removed in PR #1679. This drops
the code that only existed to serve it:

- XDCxDAO: drop the mongo-only XDCXDAO methods (GetObject, InitBulk,
  CommitBulk, InitLendingBulk, CommitLendingBulk), their empty leveldb
  implementations and the uncalled list helpers (DeleteItemByTxHash,
  GetListItemByTxHash, GetListItemByHashes); the package comment no longer
  claims a mongodb backend

- ethdb, core, eth: drop the ethdb.XDCxDatabase interface, the
  BlockChain.XDCxDb handle and the four NewBlockChainEx* wrappers;
  eth/backend.go now calls NewBlockChainResolved directly, and the startup
  test that named the wrapper goes with them

- XDCxlending: remove the uncalled Lending.GetLevelDB wrapper; the XDCx
  accessor it delegated to stays, since New still uses it, and the Lending.XDCx
  field keeps its other readers in order_processor.go

Follow-on cleanups on the same theme:

- XDCx/tradingstate: give orderList its own doc comment (it carried a copy of
  the tradingExchangeObject one) and state what tradingExchangeObject holds:
  the main trie is keyed by the order book hash, with the orderId trie as the
  sub-trie rooted at OrderRoot. The same wording corrects the two sibling
  stateObject blocks in state_orderList.go and state_orderbook.go; all three
  were carried in from b5abbfe.

- XDCxlending/order_processor.go: the liquidationData comment said the data
  goes to mongodb; it is written to the trade's extraData

- docs/xdc/debug: drop the one mgo goroutine block (a
  github.com/globalsign/mgo newcoarseTimeProvider select loop) from the
  debug_stacks_response.json fixture, left over from a capture taken while the
  mgo dependency was still in the tree; nothing in the tree mentions mgo or
  globalsign any more

The rest of this branch's original scope -- the bson marshallers and their
dedicated structs, the bson struct tags, the ToBigInt helpers, the dead Trade
type and epochpriceitem.go, and the mgo entry in go.mod/go.sum -- landed on
dev-upgrade first (#2805, #2806). After rebasing onto them those hunks are gone,
so this commit only carries what was still missing.

Comment-only and dead-code changes; no behaviour changes.

No upstream equivalent: geth has no XDCx module.
Hashes come from the official golangci-lint-2.15.0-checksums.txt; the linux-amd64 archive was downloaded and its sha256 spot-checked against that file. The .sbom.json and -source.tar.gz assets are excluded, matching the previous layout and upstream geth.

Verified with make all, plus a lint run that resolved, checksum-verified and executed the v2.15.0 binary (params package reports the same two pre-existing copyloopvar issues as before).

Ref: #2839
Update the # version:golang pin in build/checksums.txt, which selects the
distribution fetched by -dlgo for release builds, from 1.25.12 to 1.25.14.
go.mod is untouched.

The bump picks up two Go minor releases on the 1.25 line:

- go1.25.13 (released 2026-08-13) includes security fixes to the go command,
  crypto/tls, encoding/asn1, encoding/xml, html/template, net/http and
  net/url, plus bug fixes to the compiler, the runtime, crypto/tls and os.
  Milestone:
  https://github.com/golang/go/issues?q=milestone%3AGo1.25.13+label%3ACherryPickApproved
- go1.25.14 (released 2026-08-19) includes fixes to the net/http package.
  Milestone:
  https://github.com/golang/go/issues?q=milestone%3AGo1.25.14+label%3ACherryPickApproved

Release notes: https://go.dev/doc/devel/release#go1.25.13 and
https://go.dev/doc/devel/release#go1.25.14 (upstream geth cites the
golang-announce post for the same purpose, geth ethereum#35935).

The pin stays on the 1.25 line so the release toolchain matches the rest of
the repository: CI runs 1.25.x, cicd/Dockerfile builds with golang:1.25 and
go.mod declares go 1.25. Moving ahead to 1.27 is not possible yet:
golangci-lint 2.12.2 and golang.org/x/tools v0.37.0 cannot read Go 1.27
export data, so make lint and make generate break as soon as a developer
installs the pinned toolchain (measured on 1.27.0, 1.27.1, 1.27.2 and
1.27.3; upstream pins its lint job to 1.27.1 for the same reason, geth
ethereum#35927).

Hashes are taken from https://go.dev/dl/?mode=json&include=all. The archive
list is unchanged: 41 entries, freebsd-riscv64 included.

Verified by letting build.DownloadGo checksum-verify
go1.25.14.linux-amd64.tar.gz (sha256 a21ae563...), extract it and build
every command in ./cmd/... with the extracted toolchain (go version
go1.25.14); make all and make tidy pass.
…#2818)

Remove the toolchain line instead of raising it. This tree already ships
its own toolchain download system: -dlgo fetches the Go release pinned in
build/checksums.txt, which is the single source of truth for release
builds. Leaving a toolchain directive around lets Go's built-in switching
fetch a compiler behind that pin's back, or fail outright when
GOTOOLCHAIN=local runs an older Go.

Adopt geth ethereum#31057 verbatim; the same reasoning applies here, so the
directive is dropped rather than bumped.

The go directive stays at 1.25, so module resolution is unchanged.

make tidy reports no untidy module files and make all passes.

Ref: ethereum#31057
…reum#27387 ethereum#34109 (#2819)

TestUDPv5_findnodeHandling can fail when the table revalidation timer
fires while the test is waiting for NODES responses. Revalidation pings
the last node of a random non-empty bucket, and nodes inserted by
fillTable have no UDP port and are not registered in the test harness,
so the background PING is taken for an expected packet and fails with
"attempt to send to non-existing node 3.0.2.3:0".

Expose the interval as Config.PingInterval, defaulting to the existing
10s so production behaviour is unchanged, and pass it to the table. The
UDPv5 test harness sets it to 1000 hours, disabling revalidation for
tests. A test overlay that accelerates the interval reproduces the
failure without the harness setting and passes with it.

Non-positive intervals fall back to the default as well: a negative
value would otherwise reach rand.Int63n from Table.nextRevalidateTime
and panic the Table.loop goroutine. ethereum#27387 only handles zero and
upstream still does, so this part has no upstream counterpart. Very
small positive values remain allowed and will revalidate continuously;
the field comment documents that.

newTable now takes the Config and applies withDefaults on its first
line, mirroring the single-point defaulting of ethereum#27387. This fork keeps
revalidating from Table.loop instead of table_reval.go, so neither that
rewrite nor the Config field stored on Table is carried over.

The Config knob backports go-ethereum ethereum#27387, which this fork predates;
the harness setting is the fix from ethereum#34109, whose one-line change depends
on that knob. Upstream has since moved revalidation into table_reval.go.

Ref: ethereum#27387
Ref: ethereum#34109
…on (#2820)

* fix(cmd/puppeth): reject unusable XDPoS schedules in genesis generation

The interactive wizard collects the epoch and the gap as two independent
answers, so a gap of 0, one equal to the epoch or one longer than it could
reach the genesis. None of the three gives the v2 trigger a usable gap
block: with a zero gap it asks for n%Epoch == Epoch, which no height
satisfies; with a gap longer than the epoch the subtraction wraps and no
height matches either; with a gap equal to the epoch the trigger lands on
the epoch switch itself, where engine_v1's (number+Gap)%Epoch == 0 accepts
the offset, BlockChain.isGapBlockNumber (gap <= epoch) sides with engine_v1,
while BlockChain.isNextEpochGapBlock refuses it, so the checkpoint predicate
and the snapshot predicate disagree about which block carries the next
epoch's snapshot.

Judge the pair once, before anything is derived from it and before the
system contracts are deployed: the epoch is written into the BlockSigner
contract deployed further down, so failing early leaves no genesis behind
and skips the deployment work. The interactive answers are judged where they
are read, before the int to uint64 conversion can turn a negative one into
a huge positive value: an epoch below 2 and a negative gap end the run, an
explicit gap has to stay inside the epoch, and a zero gap takes the
default. The BlockSigner parameter is a uint256, so the epoch has no int64
ceiling and is deployed with SetUint64.

The interactive gap default halves the epoch instead of staying at a fixed
450, so it sits inside the bound whatever epoch was answered; at the
template epoch of 900 it is still 450. The default is derived by a helper
the test can pin directly, and the interactive run is covered end to end as
well: a child process answers the wizard, leaves the gap empty and the
stored genesis must carry half the answered epoch. The two schedule
questions are asked only when their answers are read, so the input-file
path no longer prints prompts reporting the template epoch and half of it
for a pair it does not produce.

The input file had the mirror problem for the keys it cannot apply: gap,
switchBlock and switchEpoch have no GenesisInput field, so yaml dropped
them silently and the genesis kept the cloned Localnet template's values
while the file said otherwise. Refuse such a file and name the keys, and
say in the guard that the coverage stops there: period, rewardCheckpoint,
maxMasternodesV2, skipV1Validation and v2 have no field either and are
still dropped without a word. A child process feeds a file that names gap
and the run must exit before writing, so the refusal is covered end to end.
The epoch field is the one schedule field the file can carry, so keep
applying it on top of the template and let the same check judge the
resulting pair; the input-file path is the only non-interactive way to
choose an epoch. A file with no fields at all is refused as well: an
empty file, a comment-only one and an explicitly empty mapping ({})
would all decode to nothing and silently produce the template defaults,
and yaml hands back a non-nil empty map for {}, so the guard judges the
length alongside the nil.

The bound is open-coded in checkXDPoSSchedule because the config-level rule
is not on this branch yet: it lands in ChainConfig.CheckConfigForkOrder,
and the body should then defer to it instead of restating the range.

* fix(cmd/puppeth): fold genesis input keys instead of dropping them

The input file is decoded by matching keys against the lowercased Go field
names exactly, while the schedule guard already folds case and underscores
before comparing. A file that spelled a field like its Go name (Epoch:,
Masternodes_Owner:) therefore decoded to the template default without a
word, the same silent divergence the guard refuses for gap, switchBlock and
switchEpoch.

Fold the recognised keys onto the spelling yaml matches before decoding, and
refuse a file that carries two spellings of one field instead of letting the
map order pick a winner. Keys with no GenesisInput field (period,
rewardCheckpoint, maxMasternodesV2, skipV1Validation, v2 and anything
misspelled) now warn instead of disappearing silently; they stay accepted so
existing input files keep working.

The decoder has matched exact lowercase keys ever since the input file was
introduced, so this fixes an older hole rather than one this branch opened.
…2822)

A config that declares XDPoS could carry any Epoch/Gap pair into the
engines. CheckConfigForkOrder only checks the SwitchBlock alignment and
skips it when Epoch is 0, so both engine constructors rewrote a missing
epoch to 900. The core gap predicates then disagree on Gap == Epoch, and
for Gap > Epoch the v1 engine still walks Gap blocks back from a
checkpoint to heights those predicates never report.

Validate the bound in core at every point that resolves a config before
the engine is built (Genesis.commit, SetupGenesisBlock and the
LoadChainConfig resolvers) and again where a resolved config opens a
BlockChain. params and consensus stay untouched, so a violating config
now fails at startup instead of being warned about. This replaces the
params-level default-and-continue answer of #2560.
 ethereum#31539 ethereum#35324 (#2825)

* fix(internal/testlog): compare levels in the right direction ethereum#29572

bufHandler.Enabled compared the record level the wrong way round: it
returned lvl <= h.level, so a logger configured with log.LvlTrace
(LevelTrace = -8) accepted only trace records and silently dropped
debug, info, warn and error. Every other handler in log/ uses the
opposite convention (TerminalHandler returns level >= h.lvl), and
go-ethereum fixed the same line in ethereum#29572.

Only that line is ported: the rest of ethereum#29572 rewrites peer discovery
revalidation (table_reval.go), which this tree still implements in
Table.loop, so the commit cannot be taken wholesale.

Ref: ethereum#29572

* fix(internal/testlog): log through the handler created by With ethereum#31539

logger.With copied the parent's *bufHandler into the sub-logger, but
log.Logger.With builds a new handler through bufHandler.WithAttrs and
attaches that to the returned logger. Records written through the
sub-logger therefore went to a handler that flush never read: its output
was never printed and its buffer grew without bound. Take the handler
from the logger returned by With instead; the type assertion is safe
because a testlog logger is always built on a *bufHandler.

This is also where the T interface comes from: with it, a unit test can
pass a fake that captures Logf, which *testing.T cannot. LoggerWithHandler
is removed along with it, as upstream did; nothing in this tree called
it, and Logger(t T, ...) still accepts every existing call site, all of
which pass a *testing.T.

Ref: ethereum#31539

* fix(internal/testlog): clone the attribute list before appending ethereum#35324

WithAttrs and terminalFormat appended to h.attrs directly. When the
slice has spare capacity the append writes into an array a sibling
handler still points at, so the two overwrite each other's attributes.
Clone the slice first, as upstream does.

The hazard is not reachable in this tree today: every call site applies
With at most once to a freshly built logger, whose attrs slice has no
spare capacity yet. Keeping a capacity-dependent aliasing bug in a test
helper is still not worth it when the fix is one call.

Ref: ethereum#35324

* test(internal/testlog): cover the level filter, sub-loggers and attributes

One regression per upstream fix ported on this branch:
- at log.LvlTrace an info record has to reach t.Logf (Enabled direction,
  ethereum#29572);
- a logger obtained from With has to print its own records, attribute
  included (they used to land in a handler that flush never read, ethereum#31539);
- appending attributes must not write into the slice a sibling handler
  still points at (ethereum#35324).

None of this is observable from the outside: a *testing.T cannot capture
its own Logf, which is why the T interface came with ethereum#31539.

* fix(internal/testlog): serialize With with log flushes (pre-existing race)

logger.With derived the sub-logger from a copy of the parent's buffer
(bufHandler.WithAttrs copies h.buf) without holding the logger mutex,
while every log method holds l.mu across its Handle and flush. A With
running in that window makes the child inherit a record the parent is
about to print as well, and the child's first flush prints it a second
time.

Hold l.mu across With, the same critical section the log methods use:
the buffer copy then always observes a fully flushed state. Lock order
stays l.mu -> h.mu (WithAttrs takes only h.mu), so no new deadlock is
possible.

The race predates this branch: the baseline builds sub-loggers the same
way, and go-ethereum has no corresponding fix -- internal/testlog is
unchanged upstream since ethereum#35324, even though go-ethereum derives
sub-loggers from the same logger in p2p exactly as this tree does. The
two added lines are therefore a deliberate divergence from upstream, and
the window they close is not hypothetical: p2p hands the test logger to
the server as Config.Logger, and p2p/server.go derives a sub-logger from
it in every connection goroutine while the listen loop and the peer loop
log on it concurrently.

The regression test parks a logger inside its flush -- a T that blocks in
Logf -- and requires that deriving another sub-logger from the same logger
cannot return while that log call is in flight. It does not depend on two
goroutines happening to overlap: without the lock the With returns at once
and the test fails, with the lock the With cannot return at all.
* fix(core): keep the removed-logs delivery test off the scheduler

TestReorgDeliversRemovedLogsSynchronously recorded the arrival in the
subscriber goroutine after the channel handoff, and read that flag once the
reorg returned. An unbuffered handoff only orders the send against the
receive: whatever the subscriber does next runs in parallel with the rest of
the reorg, so a subscriber the scheduler put off for a few hundred
microseconds made a synchronous send look asynchronous. The new race job
(-p 4 on a 4 vCPU runner) failed on it in #2797 with the reorg having waited
the full subscriber delay.

Announce readiness before parking on the receive instead: that write is
sequenced before the handoff, which in turn happens before the send the reorg
waits for returns. Carry the arrival over a buffered channel and compare its
log count against the chain that was reverted. Both ends of that channel are
bounded by a timeout, so a reorg that never sends fails the test without
stranding the subscriber goroutine.

Upstream geth has no equivalent test and sends synchronously as well, so
there is nothing to port.

Tests: 160 runs of the test as four processes pinned to four cores under
GOMAXPROCS=4, 7 of which failed before this change and none after; turning
the send into a goroutine still fails the test within 0.02s, and dropping the
send altogether fails it at once on the armed check, with the subscriber
goroutine then leaving the receive within its own timeout. make all, go test
./core and go run build/ci.go lint ./core/... (no new findings) pass.

* test(core): widen the removed-logs delivery budget

The subscriber delay is not only a stall: it is also how long an
asynchronous send may take before the check can no longer tell it apart
from a synchronous one. reorg hands the removals over near its start
(both rmLogsFeed.Send calls precede the rollback and the head writes), so
a spawned send has to outlast the delay before it hides. At 250ms that
window was ~12x the ~20ms of remaining reorg work measured under -race,
so a slow runner could pass a regression silently.

Raise it to a second, which leaves ~50x, and record the premise next to
the constant: the budget tracks where the send sits in reorg, not the
subscriber. Spawning the send still fails the test within 0.01s, so the
guard is unchanged.

The test and its 250ms budget predate this branch - the previous commit
only changed how the arrival is recorded - so this lands on its own
commit instead of being folded into it.

Tests: make all; go test ./core (24.3s); the test under -race (1.02s);
40 runs as four processes pinned to four cores under GOMAXPROCS=4, none
failing; lint on ./core/... (26 issues the package already had, none in
the diff).
)

SimNode.Start registers the services before the devp2p node is started, and
node.RegisterProtocols appends to the p2p server's protocol slice in that
window. The simulations HTTP API marshals the node info from another
goroutine: the event stream does it as soon as a control event is handed
over, and the node handlers do it on request. Neither side takes a lock, so a
node that is created and then started races with the HTTP layer reading the
protocol slice. The new race job in #2797 reported it as TestHTTPSnapshot
failing on p2p.Server.NodeInfo reading srv.Protocols against
node.RegisterProtocols appending to it.

Hold the startup lock for the whole of Start and take it shared in NodeInfo.
The lock order stays startMu -> node.lock, the only other lock in the path,
and nothing reached from Start calls NodeInfo, so readers wait for the
startup instead of observing a half-filled server. The change is confined to
the in-memory adapter and leaves the event and node response payloads alone.

Upstream geth deleted p2p/simulations in ethereum#30250 without fixing this, and both
NodeInfo and RegisterProtocols are still unsynchronised there, so there is
nothing to port.

Tests: 100 runs of TestHTTPSnapshot as four concurrent processes under -race
(short, count=25 each) report no race; dropping the read lock again makes 3
of the 4 processes fail, so the lock is what keeps it quiet. make all, go
test ./p2p/simulations/... pass, golangci-lint 2.12.2 on
./p2p/simulations/... reports no new findings, and make tidy reports no
untidy module files.
…Duration (#2831)

* fix(common/countdown): fix data race between SetParams and GetTimeoutDuration

ExpTimeoutDuration stored duration/base/maxExponent without synchronisation
while the countdown goroutine read them, so a concurrent SetParams could be
observed torn and a rejected parameter set could be partially visible.

Guard the three fields with a sync.RWMutex: GetTimeoutDuration copies them
under the read lock, SetParams validates the candidate set before taking the
write lock and publishes all three at once. Rollback is no longer needed
because an invalid set is never written.

The signatures are unchanged. NewExpTimeoutDuration now returns a nil helper
along with the error instead of a helper holding the rejected values; its only
caller already drops the helper on error.

Add a concurrent SetParams/GetTimeoutDuration test that reports the data race
on the old code under -race, plus a test pinning that a rejected set leaves
the timeout unchanged. The latter covers both rejection branches and keeps
all three parameters observable, so a partial write cannot slip through.

Also correct the pre-existing ")= >=" typo in the validation message now owned
by checkExpParams, and assert the previously discarded SetParams errors in
TestExpDuration.

Fixes #2828

* fix(params): correct the max_exponent upper-bound validation message

validateV2ExpTimeoutConfig printed "max_exponent (%d)= >=
max_exponent_upperbound (%d)". Drop the stray "=" so the message matches
the one emitted by checkExpParams in common/countdown for the same bound.

Message text only; the validation behaviour is unchanged.

* refactor(common/countdown): read initilised under RLock

CountdownTimer.isInitilised took the write lock to read a single bool,
while the read path of ExpTimeoutDuration takes the read lock. Use RLock
for the read so both helpers in the package follow the same rule.

No behaviour change: initilised is only written by setInitilised, and the
two helpers are never called while the other's lock is held.

Pre-existing (blame 521b703), not introduced by the SetParams and
GetTimeoutDuration race fix.
feat(eth/hooks,params): add signing-proportional reward hard fork
@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 279 files, which is 179 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 274de918-4125-4897-81c0-3d938560e213
Commits

Reviewing files that changed from the base of the PR and between 53b721d and a7d7b9b.

Files ignored due to path filters: 1
  • go.sum is excluded by !**/*.sum
Files selected for processing: 279
  • .agents/skills/use-modern-go/SKILL.md
  • .agents/skills/use-modern-go/scripts/VERSION
  • .agents/skills/use-modern-go/scripts/run-tool.ps1
  • .agents/skills/use-modern-go/scripts/run-tool.sh
  • GLOSSARY.md
  • XDCx/XDCx.go
  • XDCx/order_processor.go
  • XDCx/token.go
  • XDCx/token_test.go
  • XDCx/tradingstate/common.go
  • XDCx/tradingstate/epochpriceitem.go
  • XDCx/tradingstate/orderitem.go
  • XDCx/tradingstate/state_orderList.go
  • XDCx/tradingstate/state_orderbook.go
  • XDCx/tradingstate/trade.go
  • XDCxDAO/interfaces.go
  • XDCxDAO/leveldb.go
  • XDCxlending/XDCxlending.go
  • XDCxlending/lendingstate/common.go
  • XDCxlending/lendingstate/lendingitem.go
  • XDCxlending/lendingstate/relayer.go
  • XDCxlending/lendingstate/trade.go
  • XDCxlending/order_processor.go
  • accounts/abi/abigen/bind.go
  • accounts/abi/abigen/bind_golden_test.go
  • accounts/abi/abigen/bind_library_test.go
  • accounts/abi/abigen/source.go.tpl
  • accounts/abi/abigen/source2.go.tpl
  • accounts/abi/abigen/testdata/v1/callbackparam.go.txt
  • accounts/abi/abigen/testdata/v1/callfrom.go.txt
  • accounts/abi/abigen/testdata/v1/constructorwithstructparam.go.txt
  • accounts/abi/abigen/testdata/v1/crowdsale.go.txt
  • accounts/abi/abigen/testdata/v1/dao.go.txt
  • accounts/abi/abigen/testdata/v1/deeplynestedarray.go.txt
  • accounts/abi/abigen/testdata/v1/defaulter.go.txt
  • accounts/abi/abigen/testdata/v1/empty.go.txt
  • accounts/abi/abigen/testdata/v1/eventchecker.go.txt
  • accounts/abi/abigen/testdata/v1/eventer.go.txt
  • accounts/abi/abigen/testdata/v1/funkygaspattern.go.txt
  • accounts/abi/abigen/testdata/v1/getter.go.txt
  • accounts/abi/abigen/testdata/v1/identifiercollision.go.txt
  • accounts/abi/abigen/testdata/v1/inputchecker.go.txt
  • accounts/abi/abigen/testdata/v1/interactor.go.txt
  • accounts/abi/abigen/testdata/v1/multicontracts.go.txt
  • accounts/abi/abigen/testdata/v1/nameconflict.go.txt
  • accounts/abi/abigen/testdata/v1/newerrors.go.txt
  • accounts/abi/abigen/testdata/v1/newfallbacks.go.txt
  • accounts/abi/abigen/testdata/v1/newsinglestructargument.go.txt
  • accounts/abi/abigen/testdata/v1/nonexistent.go.txt
  • accounts/abi/abigen/testdata/v1/nonexistentstruct.go.txt
  • accounts/abi/abigen/testdata/v1/numericmethodname.go.txt
  • accounts/abi/abigen/testdata/v1/outputchecker.go.txt
  • accounts/abi/abigen/testdata/v1/overload.go.txt
  • accounts/abi/abigen/testdata/v1/pureandview.go.txt
  • accounts/abi/abigen/testdata/v1/rangekeyword.go.txt
  • accounts/abi/abigen/testdata/v1/slicer.go.txt
  • accounts/abi/abigen/testdata/v1/structs.go.txt
  • accounts/abi/abigen/testdata/v1/token.go.txt
  • accounts/abi/abigen/testdata/v1/tuple.go.txt
  • accounts/abi/abigen/testdata/v1/tupler.go.txt
  • accounts/abi/abigen/testdata/v1/underscorer.go.txt
  • accounts/abi/abigen/testdata/v2/lowercaseerrorname.go.txt
  • accounts/abi/abigen/testdata/v2/originalerrorname.go.txt
  • accounts/abi/argument.go
  • accounts/abi/bind/old_test.go
  • accounts/abi/bind/v2/internal/contracts/solc_errors/bindings.go
  • accounts/abi/bind/v2/lib_test.go
  • accounts/abi/bind/v2/metadata_test.go
  • accounts/abi/pack.go
  • accounts/abi/reflect.go
  • bmt/bmt.go
  • bmt/bmt_r.go
  • bmt/bmt_test.go
  • build/checksums.txt
  • cmd/XDC/chaincmd.go
  • cmd/XDC/consolecmd_test.go
  • cmd/XDC/dao_test.go
  • cmd/XDC/run_test.go
  • cmd/puppeth/genesis_deploy_test.go
  • cmd/puppeth/main_test.go
  • cmd/puppeth/wizard_genesis.go
  • cmd/puppeth/wizard_genesis_schedule_test.go
  • cmd/utils/cmd.go
  • cmd/utils/flags.go
  • cmd/utils/flags_test.go
  • cmd/utils/import_test.go
  • common/countdown/countdown.go
  • common/countdown/exp_duration.go
  • common/countdown/exp_duration_test.go
  • consensus/XDPoS/XDPoS.go
  • consensus/XDPoS/api.go
  • consensus/XDPoS/api_test.go
  • consensus/XDPoS/engines/engine_v1/engine.go
  • consensus/XDPoS/engines/engine_v2/engine.go
  • consensus/XDPoS/engines/engine_v2/engine_test.go
  • consensus/XDPoS/engines/engine_v2/forensics.go
  • consensus/XDPoS/engines/engine_v2/forensics_test.go
  • consensus/XDPoS/engines/engine_v2/timeout.go
  • consensus/XDPoS/utils/types.go
  • consensus/tests/engine_v1_tests/helper.go
  • consensus/tests/engine_v2_tests/forensics_test.go
  • consensus/tests/engine_v2_tests/helper.go
  • consensus/tests/engine_v2_tests/penalty_test.go
  • consensus/tests/engine_v2_tests/reward_proportional_test.go
  • consensus/tests/engine_v2_tests/reward_standby_regression_test.go
  • contracts/README.md
  • contracts/XDCx/contract/LendingRegistration.go
  • contracts/XDCx/contract/Registration.go
  • contracts/XDCx/contract/TRC21.go
  • contracts/XDCx/contract/TRC21Issuer.go
  • contracts/XDCx/contract/XDCXListing.go
  • contracts/blocksigner/blocksigner_watch_test.go
  • contracts/blocksigner/contract/blocksigner.go
  • contracts/multisigwallet/contract/multisigwallet.go
  • contracts/randomize/contract/randomize.go
  • contracts/randomize/randomize_test.go
  • contracts/randomize/statedb_utils_test.go
  • contracts/tests/contract/Inherited.go
  • contracts/trc21issuer/contract/TRC21.go
  • contracts/trc21issuer/contract/TRC21Issuer.go
  • contracts/utils.go
  • contracts/utils_test.go
  • contracts/validator/contract/validator.go
  • core/block_validator.go
  • core/block_validator_test.go
  • core/blockchain.go
  • core/blockchain_epochswitch_test.go
  • core/blockchain_executed_marker_eviction_test.go
  • core/blockchain_head_snapshot_test.go
  • core/blockchain_knownblock_test.go
  • core/blockchain_pruned_ancestors_test.go
  • core/blockchain_reorg_checkpoint_test.go
  • core/blockchain_test.go
  • core/chain_config_mismatch_policy_test.go
  • core/chain_makers.go
  • core/chain_makers_test.go
  • core/chainconfig_equal.go
  • core/chainconfig_equal_test.go
  • core/dao_test.go
  • core/forkid/forkid_trio_test.go
  • core/genesis.go
  • core/genesis_compat_test.go
  • core/genesis_load.go
  • core/genesis_load_test.go
  • core/genesis_setup.go
  • core/genesis_setup_test.go
  • core/genesis_test.go
  • core/multicall3_test.go
  • core/rawdb/accessors_chain.go
  • core/rawdb/accessors_chain_test.go
  • core/rawdb/database.go
  • core/rawdb/database_test.go
  • core/state/journal.go
  • core/state/statedb.go
  • core/state/statedb_deleteaddress_test.go
  • core/state/statedb_hooked.go
  • core/state_prefetcher.go
  • core/state_prefetcher_test.go
  • core/state_processor.go
  • core/state_processor_historical_bypass_test.go
  • core/state_processor_test.go
  • core/state_transition.go
  • core/state_transition_test.go
  • core/token_validator.go
  • core/txpool/txpool.go
  • core/types.go
  • core/types/forensics.go
  • core/types/transaction_test.go
  • core/vm/XDCx_price.go
  • core/vm/common.go
  • core/vm/common_test.go
  • core/vm/contracts_kzg_stub_test.go
  • core/vm/evm.go
  • core/vm/gas_table_test.go
  • core/vm/interface.go
  • core/vm/jump_table.go
  • core/vm/jump_table_export.go
  • core/vm/jump_table_test.go
  • core/xdpos_schedule.go
  • core/xdpos_schedule_test.go
  • docs/adr/0001-bound-executed-marker-to-state-lifetime.md
  • docs/adr/0002-require-standard-fork-sequence.md
  • docs/adr/0003-enforce-gap-within-epoch.md
  • docs/solidity.md
  • docs/upgrade.md
  • docs/xdc/XDPoS/XDPoS.md
  • docs/xdc/debug/debug_stacks_response.json
  • eth/api_admin_test.go
  • eth/api_backend.go
  • eth/api_backend_test.go
  • eth/backend.go
  • eth/backend_startup_test.go
  • eth/bft/bft_handler_test.go
  • eth/downloader/downloader_test.go
  • eth/downloader/testchain_test.go
  • eth/filters/api.go
  • eth/filters/filter_system_test.go
  • eth/filters/filter_test.go
  • eth/gasprice/gasprice_test.go
  • eth/handler_test.go
  • eth/hooks/engine_v2_hooks.go
  • eth/hooks/engine_v2_hooks_test.go
  • eth/state_accessor.go
  • eth/state_accessor_tipsigning_test.go
  • eth/tracers/api.go
  • eth/tracers/api_cancellation_test.go
  • eth/tracers/api_intermediate_roots_test.go
  • eth/tracers/api_parent_block_hash_test.go
  • eth/tracers/api_test.go
  • eth/tracers/api_tipsigning_activation_test.go
  • eth/tracers/js/tracer_test.go
  • ethclient/simulated/backend.go
  • ethclient/simulated/backend_finalized_test.go
  • ethdb/database.go
  • ethstats/ethstats.go
  • genesis/devnet.json
  • genesis/mainnet.json
  • genesis/testnet.json
  • go.mod
  • internal/ethapi/api.go
  • internal/ethapi/api_test.go
  • internal/ethapi/simulate.go
  • internal/ethapi/simulate_owned_overrides_test.go
  • internal/ethapi/simulate_tipsigning_test.go
  • internal/ethapi/transaction_args.go
  • internal/ethapi/transaction_args_test.go
  • internal/testlog/testlog.go
  • internal/testlog/testlog_test.go
  • log/format.go
  • metrics/sample_test.go
  • miner/miner.go
  • miner/worker.go
  • miner/worker_test.go
  • node/node.go
  • node/node_test.go
  • p2p/dial.go
  • p2p/dial_test.go
  • p2p/discover/common.go
  • p2p/discover/common_test.go
  • p2p/discover/table.go
  • p2p/discover/table_util_test.go
  • p2p/discover/v4_udp.go
  • p2p/discover/v4_udp_test.go
  • p2p/discover/v5_udp.go
  • p2p/discover/v5_udp_test.go
  • p2p/discover/v5wire/encoding_test.go
  • p2p/enode/iter_test.go
  • p2p/enode/nodedb_test.go
  • p2p/simulations/adapters/inproc.go
  • p2p/simulations/adapters/types.go
  • p2p/simulations/examples/ping-pong.go
  • p2p/simulations/http.go
  • p2p/simulations/http_test.go
  • p2p/simulations/mocker.go
  • p2p/simulations/network.go
  • p2p/simulations/network_test.go
  • params/config.go
  • params/config_backfill.go
  • params/config_backfill_fields.json
  • params/config_backfill_generated.go
  • params/config_backfill_generator.go
  • params/config_backfill_test.go
  • params/config_compat_test.go
  • params/config_forks.go
  • params/config_forks_test.go
  • params/config_networks.go
  • params/config_networks_test.go
  • params/config_reward_forks_test.go
  • params/config_test.go
  • params/forks/forks.go
  • params/protocol_params.go
  • rlp/decode.go
  • rlp/encode.go
  • rlp/internal/rlpstruct/rlpstruct.go
  • rlp/rlpgen/types.go
  • rlp/typecache.go
  • rpc/client.go
  • rpc/json.go
  • rpc/service.go

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

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

loglapa and others added 7 commits October 27, 2026 07:01
* fix(node): release datadir lock when initialization fails

* fix(node): clean up temporary keystore on initialization failure

Signed-off-by: Vladislav Lapin <51929896+loglapa@users.noreply.github.com>

* fix(node): close account manager on initialization failure

Signed-off-by: Vladislav Lapin <51929896+loglapa@users.noreply.github.com>

* test(node): track manager goroutines across failed initialization

Signed-off-by: Vladislav Lapin <51929896+loglapa@users.noreply.github.com>

---------

Signed-off-by: Vladislav Lapin <51929896+loglapa@users.noreply.github.com>
make lint fails on 18 whitespace violations: a blank line right
before a closing brace ("unnecessary trailing newline") or right
after an opening brace ("unnecessary leading newline") across the
consensus, core, internal and p2p packages.

golangci-lint reports at most three issues of each kind per run
(max-same-issues defaults to 3), so the debt surfaced piecemeal.
This removes all of it in one pass; no code behavior changes.
…2889)

The module targets Go 1.25, where each iteration of a for-range loop
binds a fresh instance of its variables, so the explicit per-iteration
copies ("tt := tt", "mode := mode", ...) kept to guard closures and
subtests against loop-variable reuse are no longer needed and confuse
the copyloopvar linter.

Remove all 11 copies it reports across core, eth/downloader,
p2p/discover/v5wire and params. No behaviour change: the closures now
capture the per-iteration loop variable itself.
…2892)

golangci-lint's revive receiver-naming rule flags five methods whose
receiver name deviates from the name the rest of the type uses:

- TxPool.SetSigner and TxPool.IsSigner used "pool" while the other 18
  TxPool methods use "p" (upstream geth uses "p" throughout as well)
- XDCxLastPrice.Name and XDCxEpochPrice.Name used "c" while the
  remaining methods of both types use "t"
- Miner.SetGasTip used "miner" while the other 14 Miner methods use "m"

Each rename follows the majority convention already established in its
file, so the diff is a pure rename with no behavior change.
The unconvert linter flags a conversion when the value already has the
target type. Four such conversions survived in the tree:

- uint64(params.TxGas): TxGas is already declared uint64
- types.Transactions(block.Transactions()): Block.Transactions()
  already returns types.Transactions
- []byte(obj.code): the state object code field is already []byte
- int(n.LogVerbosity): NodeConfig.LogVerbosity is already int

Remove them. No behaviour change. The sibling conversion
types.Transactions(entry.Transactions) stays: Body.Transactions is a
plain []*Transaction slice, so that one is load-bearing.
…eum#35176 (#2894)

The govet inline check added in the go1.26 toolchain flags two patterns
across the tree, and make lint reports 21 of them - the last linter
debt nobody has claimed:

  - reflect.Ptr, the deprecated alias whose constant value is now
    trivially inlinable, in accounts/abi, log, rlp and rpc (19 hits);
  - ioutil.ReadFile and ioutil.TempDir in two p2p tests, which can no
    longer be inlined into files compiled with an older language
    version (2 hits).

Swap reflect.Ptr for reflect.Pointer, mirroring the upstream sweep in
ethereum#35176, and move the two test call sites onto the
os package equivalents (os.ReadFile, os.MkdirTemp) that upstream
already uses. No behaviour change; a cold golangci-lint run with
--enable-only govet reports zero issues.

Ref: ethereum#35176
@benjamin202410
benjamin202410 merged commit cb1ef79 into main Oct 27, 2026
27 checks passed
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.

6 participants