core, params, miner: merge geth v1.17.1 (v1.17.4 sync, milestone 3/6) - #2325
Conversation
Adds `--opcode.count=<file>` flag to `evm t8n` that writes per-opcode execution frequency counts to a JSON file (relative to `--output.basedir`). --------- Co-authored-by: MariusVanDerWijden <m.vanderwijden@live.de> Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
In src/ethereum/forks/amsterdam/vm/interpreter.py:299-304, the caller
address is
only tracked for block level accessList when there's a value transfer:
```python
if message.should_transfer_value and message.value != 0:
# Track value transfer
sender_balance = get_account(state, message.caller).balance
recipient_balance = get_account(state, message.current_target).balance
track_address(message.state_changes, message.caller) # Line 304
```
Since system transactions have should_transfer_value=False and value=0,
this condition is never met, so the caller (SYSTEM_ADDRESS) is not
tracked.
This condition is applied for the syscall in the geth implementation,
aligning with the spec of EIP7928.
---------
Co-authored-by: Felix Lange <fjl@twurst.com>
…33865) Reverts ethereum/go-ethereum#33747. This change suffers an unexpected issue during the sync with `history.chain=postmerge`.
…ncoded packet (#31547) This changes the challenge resend logic again to use the existing `ChallengeData` field of `v5wire.Whoareyou` instead of storing a second copy of the packet in `Whoareyou.Encoded`. It's more correct this way since `ChallengeData` is supposed to be the data that is used by the ID verification procedure. Also adapts the cross-client test to verify this behavior. Follow-up to #31543
The PR exposes the InfuxDB reporting interval as a CLI parameter, which was previously fixed 10s. Default is still kept at 10s. Note that decreasing the interval comes with notable extra traffic and load on InfluxDB.
implements https://github.com/ethereum/execution-apis/pull/710/changes#r2712256529 --------- Co-authored-by: Felix Lange <fjl@twurst.com>
Downgrades beacon syncer reorging from Error to Debug closes ethereum/go-ethereum#29916
Fixes an issue where AuthorizationList wasn't copied over when estimating gas for a user-provided transaction.
Implements the new eth_getStorageValues method. It returns storage values for a list of contracts. Spec: ethereum/execution-apis#756 --------- Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
…790) All five `revert*Request` functions (account, bytecode, storage, trienode heal, bytecode heal) remove the request from the tracked set but never restore the peer to its corresponding idle pool. When a request times out and no response arrives, the peer is permanently lost from the idle pool, preventing new work from being assigned to it. In normal operation mode (snap-sync full state) this bug is masked by pivot movement (which resets idle pools via new Sync() cycles every ~15 minutes) and peer churn (reconnections re-add peers via Register()). However in scenarios like the one I have running my (partial-stateful node)[ethereum/go-ethereum#33764] with long-running sync cycles and few peers, all peers can eventually leak out of the idle pools, stalling sync entirely. Fix: after deleting from the request map, restore the peer to its idle pool if it is still registered (guards against the peer-drop path where Unregister already removed the peer). This mirrors the pattern used in all five On* response handlers. This only seems to manifest in peer-thirstly scenarios as where I find myself when testing snapsync for the partial-statefull node). Still, thought was at least good to raise this point. Unsure if required to discuss or not
The fetcher should not fetch transactions that are already on chain. Until now we were only checking in the txpool, but that does not have the old transaction. This was leading to extra fetches of transactions that were announced by a peer but are already on chain. Here we extend the check to the chain as well.
To align with the latest spec of EIP-7928: ``` # CodeChange: [block_access_index, new_code] CodeChange = [BlockAccessIndex, Bytecode] ```
https://eips.ethereum.org/EIPS/eip-7928 spec: > Precompiled contracts: Precompiles MUST be included when accessed. If a precompile receives value, it is recorded with a balance change. Otherwise, it is included with empty change lists. The precompiled contracts are not explicitly touched when they are invoked since Amsterdam fork.
From the https://eips.ethereum.org/EIPS/eip-7928 > SELFDESTRUCT (in-transaction): Accounts destroyed within a transaction MUST be included in AccountChanges without nonce or code changes. However, if the account had a positive balance pre-transaction, the balance change to zero MUST be recorded. Storage keys within the self-destructed contracts that were modified or read MUST be included as a storage_reads entry. The storage read against the empty contract (zero storage) should also be recorded in the BAL's readlist.
inside tx.GasPrice()/GasFeeCap()/GasTipCap() already new a big.Int.
bench result:
```
goos: darwin
goarch: arm64
pkg: github.com/ethereum/go-ethereum/core
cpu: Apple M4
│ old.txt │ new.txt │
│ sec/op │ sec/op vs base │
TransactionToMessage-10 240.1n ± 7% 175.1n ± 7% -27.09% (p=0.000 n=10)
│ old.txt │ new.txt │
│ B/op │ B/op vs base │
TransactionToMessage-10 544.0 ± 0% 424.0 ± 0% -22.06% (p=0.000 n=10)
│ old.txt │ new.txt │
│ allocs/op │ allocs/op vs base │
TransactionToMessage-10 17.00 ± 0% 11.00 ± 0% -35.29% (p=0.000 n=10)
```
benchmark code:
```
// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)
// BenchmarkTransactionToMessage benchmarks the TransactionToMessage function.
func BenchmarkTransactionToMessage(b *testing.B) {
key, _ := crypto.GenerateKey()
signer := types.LatestSigner(params.TestChainConfig)
to := common.HexToAddress("0x000000000000000000000000000000000000dead")
// Create a DynamicFeeTx transaction
txdata := &types.DynamicFeeTx{
ChainID: big.NewInt(1),
Nonce: 42,
GasTipCap: big.NewInt(1000000000), // 1 gwei
GasFeeCap: big.NewInt(2000000000), // 2 gwei
Gas: 21000,
To: &to,
Value: big.NewInt(1000000000000000000), // 1 ether
Data: []byte{0x12, 0x34, 0x56, 0x78},
AccessList: types.AccessList{
types.AccessTuple{
Address: common.HexToAddress("0x0000000000000000000000000000000000000001"),
StorageKeys: []common.Hash{
common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"),
},
},
},
}
tx, _ := types.SignNewTx(key, signer, txdata)
baseFee := big.NewInt(1500000000) // 1.5 gwei
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := TransactionToMessage(tx, signer, baseFee)
if err != nil {
b.Fatal(err)
}
}
}
l
```
This pr adds a tool names `inpsect-trie`, aimed to analyze the mpt and its node storage more efficiently. ## Example ./geth db inspect-trie --datadir server/data-seed/ latest 4000 ## Result - MPT shape - Account Trie - Top N Storage Trie ``` +-------+-------+--------------+-------------+--------------+ | - | LEVEL | SHORTNODECNT | FULLNODECNT | VALUENODECNT | +-------+-------+--------------+-------------+--------------+ | - | 0 | 0 | 1 | 0 | | - | 1 | 0 | 16 | 0 | | - | 2 | 76 | 32 | 74 | | - | 3 | 66 | 1 | 66 | | - | 4 | 2 | 0 | 2 | | Total | 144 | 50 | 142 | +-------+-------+--------------+-------------+--------------+ AccountTrie +-------+-------+--------------+-------------+--------------+ | - | LEVEL | SHORTNODECNT | FULLNODECNT | VALUENODECNT | +-------+-------+--------------+-------------+--------------+ | - | 0 | 0 | 1 | 0 | | - | 1 | 0 | 16 | 0 | | - | 2 | 108 | 84 | 104 | | - | 3 | 195 | 5 | 195 | | - | 4 | 10 | 0 | 10 | | Total | 313 | 106 | 309 | +-------+-------+--------------+-------------+--------------+ ContractTrie-0xc874e65ccffb133d9db4ff637e62532ef6ecef3223845d02f522c55786782911 +-------+-------+--------------+-------------+--------------+ | - | LEVEL | SHORTNODECNT | FULLNODECNT | VALUENODECNT | +-------+-------+--------------+-------------+--------------+ | - | 0 | 0 | 1 | 0 | | - | 1 | 0 | 16 | 0 | | - | 2 | 57 | 14 | 56 | | - | 3 | 33 | 0 | 33 | | Total | 90 | 31 | 89 | +-------+-------+--------------+-------------+--------------+ ContractTrie-0x1d7dcb6a0ce5227c5379fc5b0e004561d7833b063355f69bfea3178f08fbaab4 +-------+-------+--------------+-------------+--------------+ | - | LEVEL | SHORTNODECNT | FULLNODECNT | VALUENODECNT | +-------+-------+--------------+-------------+--------------+ | - | 0 | 0 | 1 | 0 | | - | 1 | 5 | 8 | 5 | | - | 2 | 16 | 1 | 16 | | - | 3 | 2 | 0 | 2 | | Total | 23 | 10 | 23 | +-------+-------+--------------+-------------+--------------+ ContractTrie-0xaa8a4783ebbb3bec45d3e804b3c59bfd486edfa39cbeda1d42bf86c08a0ebc0f +-------+-------+--------------+-------------+--------------+ | - | LEVEL | SHORTNODECNT | FULLNODECNT | VALUENODECNT | +-------+-------+--------------+-------------+--------------+ | - | 0 | 0 | 1 | 0 | | - | 1 | 9 | 3 | 9 | | - | 2 | 7 | 1 | 7 | | - | 3 | 2 | 0 | 2 | | Total | 18 | 5 | 18 | +-------+-------+--------------+-------------+--------------+ ContractTrie-0x9d2804d0562391d7cfcfaf0013f0352e176a94403a58577ebf82168a21514441 +-------+-------+--------------+-------------+--------------+ | - | LEVEL | SHORTNODECNT | FULLNODECNT | VALUENODECNT | +-------+-------+--------------+-------------+--------------+ | - | 0 | 0 | 1 | 0 | | - | 1 | 6 | 4 | 6 | | - | 2 | 8 | 0 | 8 | | Total | 14 | 5 | 14 | +-------+-------+--------------+-------------+--------------+ ContractTrie-0x17e3eb95d0e6e92b42c0b3e95c6e75080c9fcd83e706344712e9587375de96e1 +-------+-------+--------------+-------------+--------------+ | - | LEVEL | SHORTNODECNT | FULLNODECNT | VALUENODECNT | +-------+-------+--------------+-------------+--------------+ | - | 0 | 0 | 1 | 0 | | - | 1 | 5 | 3 | 5 | | - | 2 | 7 | 0 | 7 | | Total | 12 | 4 | 12 | +-------+-------+--------------+-------------+--------------+ ContractTrie-0xc017ca90c8aa37693c38f80436bb15bde46d7b30a503aa808cb7814127468a44 Contract Trie, total trie num: 142, ShortNodeCnt: 620, FullNodeCnt: 204, ValueNodeCnt: 615 ``` --------- Co-authored-by: lightclient <lightclient@protonmail.com> Co-authored-by: MariusVanDerWijden <m.vanderwijden@live.de>
Previously, handshake timeouts were recorded as generic peer errors instead of timeout errors. waitForHandshake passed a raw p2p.DiscReadTimeout into markError, but markError classified errors only via errors.Unwrap(err), which returns nil for non-wrapped errors. As a result, the timeoutError meter was never incremented and all such failures fell into the peerError bucket. This change makes markError switch on the base error, using errors.Unwrap(err) when available and falling back to the original error otherwise. With this adjustment, p2p.DiscReadTimeout is correctly mapped to timeoutError, while existing behaviour for the other wrapped sentinel errors remains unchanged --------- Co-authored-by: lightclient <lightclient@protonmail.com>
Co-authored-by: tellabg <249254436+tellabg@users.noreply.github.com> Co-authored-by: lightclient <lightclient@protonmail.com>
Implements the slotnum opcode as specified here: https://eips.ethereum.org/EIPS/eip-7843
The`plucky` and `oracular` have reached end of life. That's why launchpad isn't building them anymore: https://launchpad.net/~ethereum/+archive/ubuntu/ethereum/+packages.
We didn't upgrade to 1.25, so this jumps over one version. I want to upgrade all builds to Go 1.26 soon, but let's start with the Docker build to get a sense of any possible issues.
The endianness was wrong, which means that the code chunks were stored in the wrong location in the tree.
fix the flaky test found in https://ci.appveyor.com/project/ethereum/go-ethereum/builds/53601688/job/af5ccvufpm9usq39 1. increase the timeout from 3+1s to 15s, and use timer instead of sleep(in the CI env, it may need more time to sync the 1024 blocks) 2. add `synced.Load()` to ensure the full async chain is finished Signed-off-by: Delweng <delweng@gmail.com>
…develop-drift cascade)
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
|
codegenie review |
|
@claude review |
🧞 Codegenie ReviewWarning Review incomplete. Some review or verification work did not complete. Treat the results below as partial and consider rerunning.
CoveragePartial review: 60 hunks were not reviewed because budget was exhausted before dispatch.
|
There was a problem hiding this comment.
Pull request overview
Milestone 3/6 of the Bor ← go-ethereum v1.17.4 upstream sync, merging geth v1.17.1 changes into Bor and introducing the dormant (block-scheduled) Amsterdam fork surface (EIP-7843 SLOTNUM, EIP-8024 wiring, and precompile “touch” for BAL), plus related tooling/test updates.
Changes:
- Add Amsterdam fork scheduling to
params.ChainConfig(block-based, nil-by-default) and wire VM/header surfaces forslotNumber/SLOTNUMbehindIsAmsterdam. - Add/extend trie inspection & stats utilities (LevelStats, inspect/summarize tests) and a new
geth db inspect-triecommand. - Update several networking/tooling components (discv5 WHOAREYOU resend caching, request tracker scheduling, HTTP/2 toggles, metrics InfluxDB interval, new
eth_getStorageValuesRPC, internal tablewriter stub).
Reviewed changes
Copilot reviewed 97 out of 102 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| trie/proof.go | Range proof verification: stronger firstKey constraints and error-handling for trie updates. |
| trie/levelstats.go | New per-level trie node statistics collector used by inspection/stats tooling. |
| trie/levelstats_test.go | Tests for LevelStats depth bounds behavior. |
| trie/inspect_test.go | New tests covering trie Inspect/Summarize and contract inspection paths. |
| trie/bintrie/trie.go | Binary trie code-chunk key offset encoding change (endianness). |
| tests/gen_btheader.go | JSON marshal/unmarshal support for SlotNumber in block test headers. |
| tests/block_test_util.go | SlotNumber added to block test header structure and validation. |
| params/config.go | AmsterdamBlock + IsAmsterdam + Rules.IsAmsterdam wiring (dormant by default). |
| p2p/tracker/tracker.go | Tracker clean/schedule safety tweaks and schedule refactor. |
| p2p/discover/v5wire/msg.go | WHOAREYOU struct reshaping and ChallengeData semantics. |
| p2p/discover/v5wire/encoding.go | WHOAREYOU resend encoding via cached ChallengeData; masking refactors. |
| p2p/discover/v5wire/encoding_test.go | Test ensuring WHOAREYOU resend encoding is byte-identical. |
| p2p/discover/v5_udp_test.go | Update test codec to align with new WHOAREYOU resend caching fields. |
| node/rpcstack.go | Add per-server HTTP/2 disable switch for RPC stacks. |
| node/node.go | Disable HTTP/2 on auth/Engine API HTTP servers. |
| miner/worker.go | Populate SlotNumber post-Amsterdam (errors if missing when active). |
| miner/payload_building.go | Include slot number in payload ID; recommit timer reset uses remaining time. |
| metrics/config.go | Add configurable InfluxDB reporting interval to metrics config. |
| internal/web3ext/web3ext.go | Add web3 extension for eth_getStorageValues. |
| internal/tablewriter/database_tablewriter.go | Internal tablewriter stub moved to internal/tablewriter package; API tweaks. |
| internal/tablewriter/database_tablewriter_test.go | Update tests for internal tablewriter stub API changes. |
| internal/ethapi/transaction_args.go | Carry AuthorizationList through tx defaults. |
| internal/ethapi/api.go | Add eth_getStorageValues; include SlotNumber in RPC header marshal. |
| internal/ethapi/api_test.go | Tests for GetStorageValues behavior and request limits. |
| graphql/graphql.go | Expose SlotNumber on GraphQL Block type. |
| go.mod | Bump github.com/ethereum/c-kzg-4844/v2 to v2.1.6; drop olekukonko/tablewriter. |
| go.sum | Update sums for c-kzg bump and tablewriter removal. |
| eth/tracers/native/opcode_counter.go | New native tracer: counts opcode executions. |
| eth/tracers/native/mux.go | Refactor mux tracer construction; add exported NewMuxTracer. |
| eth/protocols/snap/sync.go | Restore peer idlers on request revert when peer still present. |
| docs/upstream-merges/v1.17.4/plan.md | Mark v1.17.1 batches as merged with commit SHAs. |
| docs/upstream-merges/v1.17.4/needs-wiring.md | Track deferred items introduced in this milestone. |
| docs/upstream-merges/v1.17.4/fork-register.md | Document Amsterdam fork surface as block-gated and dormant. |
| core/vm/opcodes.go | Add SLOTNUM opcode constant and string mappings. |
| core/vm/jump_table.go | Add Amsterdam instruction set and dispatch wiring. |
| core/vm/evm.go | SLOTNUM block context + Amsterdam jump table selection; syscall transfer gating; precompile-touch plumbing. |
| core/vm/eips.go | Add 7843 activator and implement SLOTNUM opcode. |
| core/vm/contracts.go | RunPrecompiledContract now optionally “touches” precompile in StateDB post-Amsterdam. |
| core/vm/contracts_test.go | Update precompile tests for new RunPrecompiledContract signature; include IsAmsterdam in parity checklist. |
| core/vm/contracts_fuzz_test.go | Update fuzz harness for new RunPrecompiledContract signature. |
| core/types/transaction.go | Add big.Int fallback for effective tip comparisons when uint256 calc errors. |
| core/types/gen_header_rlp.go | Include SlotNumber in optional RLP header encoding sequence. |
| core/types/gen_header_json.go | Include SlotNumber in header JSON marshal/unmarshal. |
| core/types/block.go | Add SlotNumber field to Header; copy/accessors. |
| core/types/bal/bal.go | Change construction BAL code-change representation to map[txIndex][]byte. |
| core/types/bal/bal_test.go | Adjust tests for new BAL code-change representation/encoding types. |
| core/types/bal/bal_encoding.go | Update BAL encoding to support multiple code changes; add validation/copy logic. |
| core/types/bal/bal_encoding_rlp_generated.go | Regenerated RLP encoding to match BAL encoding structure changes. |
| core/txpool/legacypool/list_test.go | Add tests for price heap comparison across basefee scenarios. |
| core/txpool/blobpool/priority.go | Adjust eviction priority math; add blobfee-specific jump base. |
| core/txpool/blobpool/priority_test.go | Update tests to match new eviction priority behavior. |
| core/txpool/blobpool/evictheap.go | Use blobfee-specific jump calculation; simplify priority clamp. |
| core/txpool/blobpool/evictheap_test.go | Update sorting tests/benchmarks for new blobfee jump behavior. |
| core/txpool/blobpool/blobpool_test.go | Update expected jump constants and test chain fee setup. |
| core/stateless/stats.go | Switch witness leaf depth collection to trie.LevelStats and log/metric reporting changes. |
| core/stateless/stats_test.go | Update witness stats tests; add deep-leaf panic and aggregation coverage. |
| core/state/state_object.go | Ensure storage reader is invoked for destructed objects to record BAL reads. |
| core/state_transition.go | Use tx getters directly when building Message (copies returned by getters). |
| core/state_processor_test.go | Ensure SlotNumber is set in generated headers when Amsterdam is active. |
| core/rawdb/database.go | Switch to internal tablewriter; update constructor call. |
| core/rawdb/accessors_chain.go | Canonical HasBody/HasReceipts behavior changed. |
| core/parallel_state_processor_fork_parity_test.go | Add IsAmsterdam to fork expectations (not state-processor gated). |
| core/genesis.go | Add SlotNumber to genesis struct and header construction post-Amsterdam. |
| core/gen_genesis.go | Add SlotNumber to genesis JSON marshal/unmarshal. |
| core/evm.go | Populate vm.BlockContext.SlotNum from header.SlotNumber. |
| consensus/ethash/consensus.go | Enforce SlotNumber must be nil for ethash headers; panics in SealHash if set. |
| consensus/clique/clique.go | Enforce SlotNumber must be nil for clique headers; panic if present in signature header. |
| consensus/beacon/consensus.go | Enforce SlotNumber presence/absence based on IsAmsterdam. |
| cmd/utils/flags.go | Add --top, --output, and --metrics.influxdb.interval flags; use interval in exporters. |
| cmd/geth/main.go | Wire metrics influxdb interval flag into geth command flags. |
| cmd/geth/dbcmd.go | Add db inspect-trie command and switch metadata table output to internal tablewriter. |
| cmd/geth/config.go | Apply --metrics.influxdb.interval to config. |
| cmd/geth/chaincmd.go | Wire metrics influxdb interval flag into chain subcommands. |
| cmd/evm/testdata/33/exp.json | Normalize logs field to [] instead of null in expected output. |
| cmd/evm/testdata/30/exp.json | Normalize logs field to [] instead of null in expected output. |
| cmd/evm/testdata/3/exp.json | Normalize logs field to [] instead of null in expected output. |
| cmd/evm/testdata/29/exp.json | Normalize logs field to [] instead of null in expected output. |
| cmd/evm/testdata/28/exp.json | Normalize logs field to [] instead of null in expected output. |
| cmd/evm/testdata/25/exp.json | Normalize logs field to [] instead of null in expected output. |
| cmd/evm/testdata/24/exp.json | Normalize logs field to [] instead of null in expected output. |
| cmd/evm/testdata/23/exp.json | Normalize logs field to [] instead of null in expected output. |
| cmd/evm/testdata/13/exp2.json | Normalize logs field to [] instead of null in expected output. |
| cmd/evm/testdata/1/exp.json | Normalize logs field to [] instead of null in expected output. |
| cmd/evm/main.go | Add opcode-count flag to evm t8n command flags. |
| cmd/evm/internal/t8ntool/transition.go | Add opcode counter tracing and optional muxing with existing tracers. |
| cmd/evm/internal/t8ntool/gen_stenv.go | Add SlotNumber to state test env JSON codec. |
| cmd/evm/internal/t8ntool/gen_header.go | Add SlotNumber to header JSON codec. |
| cmd/evm/internal/t8ntool/flags.go | Add --opcode.count flag. |
| cmd/evm/internal/t8ntool/file_tracer.go | Refactor file/result writers to return tracers instead of bare hooks. |
| cmd/evm/internal/t8ntool/execution.go | Add SlotNumber to env/header construction; normalize empty receipt logs to []. |
| cmd/evm/internal/t8ntool/block.go | Add SlotNumber to block/header JSON structures. |
| cmd/devp2p/internal/v5test/discv5tests.go | Update discv5 test to validate WHOAREYOU resend behavior. |
| build/ci.go | Remove EOL Ubuntu distros from CI images list. |
| beacon/engine/types.go | Add PayloadV4 and SlotNumber fields to Engine API payload types. |
| beacon/engine/gen_ed.go | Generated JSON codec updated for ExecutableData SlotNumber. |
| beacon/engine/gen_blockparams.go | Generated JSON codec updated for PayloadAttributes SlotNumber. |
| beacon/blsync/engineclient.go | Suppress specific ForkchoiceUpdated error during blsync reorg skipping. |
| .github/CODEOWNERS | Add CODEOWNERS entry for cmd/keeper. |
Files not reviewed (4)
- beacon/engine/gen_blockparams.go: Generated file
- beacon/engine/gen_ed.go: Generated file
- cmd/evm/internal/t8ntool/gen_header.go: Generated file
- cmd/evm/internal/t8ntool/gen_stenv.go: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🧞 Codegenie Review
Warning
Review incomplete. Some review or verification work did not complete. Treat the results below as partial and consider rerunning.
Partial review: 60 hunks were not reviewed because budget was exhausted before dispatch.
Reviewed 155/264 hunks before stopping.
Coverage disclosure:
- Budget stopped review work (token limit reached).
- Verification incomplete for 6 candidates.
- beacon/engine/gen_blockparams.go: generated file
- beacon/engine/gen_ed.go: generated file
- cmd/evm/internal/t8ntool/gen_header.go: generated file
- cmd/evm/internal/t8ntool/gen_stenv.go: generated file
- core/gen_genesis.go: generated file
- core/types/bal/bal_encoding_rlp_generated.go: generated file
- core/types/gen_header_json.go: generated file
- core/types/gen_header_rlp.go: generated file
- go.sum: lockfile
- tests/gen_btheader.go: generated file
- semantic composition skipped; deterministic fallback used
Summary-only findings:
-
⚪ Low: LevelStats.AddLeaf panics on depth >= 16 and the witness-stats caller passes unclamped path length (
trie/levelstats_test.go:36)
Impact: WitnessStats.Add calls trie.LevelStats.AddLeaf(len(path)) with an unclamped node-path length. A witness node path of 16 or more nibbles (a deep account/storage branch, reachable in principle by grinding a long hashed-key prefix collision) indexes s.level[depth] beyond the fixed [16]stat array and panics, aborting the block-processing goroutine instead of degrading the metric. The newly added test locks this panic in as the intended contract rather than requiring a clamp or drop.
A statistics collector should saturate or drop out-of-range samples rather than panic, and encoding the panic as an asserted contract in tests makes the sharp edge durable. Impact is bounded: the only caller is reachable only when both VmConfig.StatelessSelfValidation and VmConfig.EnableWitnessStats are enabled, a debug/self-validation configuration that core/blockchain.go states should never run in production, so this is a robustness/contract-confirmation issue rather than a node-crash risk for default operators.Evidence:
Changed code:
func TestLevelStatsAddLeafPanicsOnDepth16(t *testing.T) { defer func() { if r := recover(); r == nil { t.Fatal("expected panic for depth >= 16") } }() NewLevelStats().AddLeaf(16) }
trie/levelstats.go↗ (Confirmed by exact read of lines 18-95: AddLeaf indexes a fixed [16]stat array with no bounds guard, so depth >= 16 is an unrecovered index-out-of-range panic. The type comment notes tries may reach 64 levels.):const trieStatLevels = 16 type LevelStats struct { level [trieStatLevels]stat } // AddLeaf records a leaf depth. Witness collection reuses the value-node bucket // for leaf accounting. It panics if the depth is outside [0, 15]. func (s *LevelStats) AddLeaf(depth int) { s.level[depth].value.Add(1) }
core/stateless/stats.go↗ (The only production caller passes the raw nibble-path length with no clamp, so any witness node path of 16+ nibbles reaches the unguarded index.):for i, path := range paths { // If current path is a prefix of the next path, it's not a leaf. // The last path is always a leaf. if i == len(paths)-1 || !strings.HasPrefix(paths[i+1], paths[i]) { ownerStat.AddLeaf(len(path)) } }
core/blockchain.go↗ (Bounds the blast radius: the collector is only constructed under the doubly-gated StatelessSelfValidation + EnableWitnessStats debug configuration, so the panic cannot be triggered on a default production node.):// Self validation should *never* run in production, it's more of // a tight integration to enable running *all* consensus tests through the // witness builder/runner ... if witness = statedb.Witness(); witness != nil && bc.cfg.VmConfig.StatelessSelfValidation { ... if bc.cfg.VmConfig.EnableWitnessStats { witnessStats = stateless.NewWitnessStats() }
Suggested fix: Either make AddLeaf tolerant (
if depth < 0 || depth >= trieStatLevels { return }, or clamp into the last bucket) and update both trie/levelstats_test.go and core/stateless/stats_test.go to assert the safe behavior, or explicitly confirm that panicking on depth >= 16 is the intended contract given the caller is debug-gated.Suggested test: If the clamp is adopted, replace TestLevelStatsAddLeafPanicsOnDepth16 with a test asserting AddLeaf(16) is a no-op (or increments the depth-15 bucket) and update core/stateless TestWitnessStatsPanicsOnDeepLeaf to assert WitnessStats.Add tolerates a 20-nibble node path.
-
⚪ Low: inspect-trie: nil header dereference when canonical hash exists but header record is missing (
cmd/geth/dbcmd.go:117)
Impact: Runninggeth db inspect-trie <blocknum>(orlatest) against a datadir where rawdb.ReadCanonicalHash returns a non-zero hash but the corresponding header record is missing or its RLP is corrupt makes rawdb.ReadHeader return nil; the very next statementtrieRoot = blockHeader.Rootdereferences that nil pointer and the CLI panics with a stack trace instead of returning the intended "header not found" error.
This is a new operator-facing debug subcommand whose stated purpose is inspecting state/trie data on databases that may be partially synced, truncated, or damaged — exactly the conditions that produce a canonical-hash entry without a readable header. A nil-pointer panic gives no actionable diagnostic and reads like a crash bug, while the surrounding code already demonstrates the intended error-return style for the adjacent canonical-hash lookup.Evidence:
Changed code:
if number != math.MaxUint64 { hash = rawdb.ReadCanonicalHash(db, number) if hash == (common.Hash{}) { return fmt.Errorf("canonical hash for block %d not found", number) } blockHeader := rawdb.ReadHeader(db, hash, number) trieRoot = blockHeader.Root }
core/rawdb/accessors_chain.go↗ (Decisive helper branch: ReadHeader returns nil both when the header RLP is absent and when it fails to decode, so blockHeader.Root at dbcmd.go:500 can dereference nil.):func ReadHeader(db ethdb.Reader, hash common.Hash, number uint64) *types.Header { data := ReadHeaderRLP(db, hash, number) if len(data) == 0 { return nil } header := new(types.Header) if err := rlp.DecodeBytes(data, header); err != nil { log.Error("Invalid block header RLP", "hash", hash, "err", err) return nil } return header }
Suggested fix: blockHeader := rawdb.ReadHeader(db, hash, number)
if blockHeader == nil {
return fmt.Errorf("header for block %d (%#x) not found", number, hash)
}
trieRoot = blockHeader.RootSuggested test: Add a cmd/geth or rawdb-backed unit test that writes only the canonical hash mapping for a block number (no header record) and asserts inspectTrie returns an error containing "header ... not found" rather than panicking.
-
⚪ Low: TestHandshakeResend drops PONG-after-resend coverage; resp.Node assignment is dead (
cmd/devp2p/internal/v5test/discv5tests.go:200)
Impact: In head,resp.Node = conn.remoteis the last statement of the case and the function returns, so the mutatedrespis never used. The test now only asserts that the second WHOAREYOU repeats the first challenge's Nonce and ChallengeData. Base asserted, via conn.reqresp, that the second PING could actually complete the handshake and yield a valid PONG. A regression in which the resent ChallengeData is echoed correctly but is stale/unusable for deriving session keys (exactly the area touched by switching from Whoareyou.Encoded to ChallengeData) would pass this test.
This is the dedicated conformance test for handshake challenge resend in the devp2p test suite, run against third-party discv5 implementations. Losing the 'answer the resent challenge and get a PONG' assertion narrows the test to a byte-equality check on the challenge and leaves the resend path's usability untested, while the leftover dead assignment signals the follow-up write/PONG check was dropped unintentionally.Evidence:
Changed code:
conn.write(l1, ping2, nil) switch resp := conn.read(l1).(type) { case *v5wire.Whoareyou: if resp.Nonce != challenge1.Nonce { ... } if !bytes.Equal(resp.ChallengeData, challenge1.ChallengeData) { ... } resp.Node = conn.remote default: t.Fatal("expected WHOAREYOU, got", resp) } }
cmd/devp2p/internal/v5test/discv5tests.go↗ (Base version (TestPingHandshakeInterrupted) completed the handshake via conn.reqresp and asserted a valid PONG for ping2; head no longer performs this step, removing the covered boundary.):// Send second PING. ping2 := &v5wire.Ping{ReqID: conn.nextReqID()} switch resp := conn.reqresp(l1, ping2).(type) { case *v5wire.Pong: checkPong(t, resp, ping2, l1)
cmd/devp2p/internal/v5test/discv5tests.go↗ (In TestPingMultiIP the sameresp.Node = ...assignment is immediately followed by writing the handshake packet and checking the PONG, showing the assignment is only meaningful when the challenge is answered.):resp.Node = s.Dest conn.write(l2, ping2, resp) ... case *v5wire.Pong: checkPong(t, resp, ping2, l2)
Suggested fix: Complete the test as the leftover assignment implies:
resp.Node = conn.remote
conn.write(l1, ping2, resp)
}
// Catch the PONG for ping2.
switch resp := conn.read(l1).(type) {
case *v5wire.Pong:
checkPong(t, resp, ping2, l1)
default:
t.Fatal("expected PONG, got", resp)
}
Alternatively, if only the challenge-repeat property is intended, remove the deadresp.Node = conn.remoteline and note in the comment that handshake completion is intentionally out of scope.Suggested test: Extend TestHandshakeResend to answer the resent WHOAREYOU with the handshake packet for ping2 and assert a valid PONG via checkPong, covering that the resent ChallengeData is usable for session establishment.
-
⚪ Low: Unchecked nil header dereference in
geth db inspect-trie(cmd/geth/dbcmd.go:500)
Impact: rawdb.ReadHeader returns a nil *types.Header when no header is stored for (hash, number) or when the stored header RLP fails to decode. The followingblockHeader.Rootthen dereferences nil and panics, sogeth db inspect-trie <blocknum>crashes with a stack trace on an inconsistent or corrupted datadir (canonical-hash marker present without a readable header) instead of returning the clean CLI error used one line earlier for a missing canonical hash.
This is an offline diagnostic command intended to be run against damaged or unusual databases; the exact situation it is used to investigate is the one where it panics instead of reporting the problem. The guard is three lines and matches the surrounding error-handling style.Evidence:
Changed code:
blockHeader := rawdb.ReadHeader(db, hash, number) trieRoot = blockHeader.Root
core/rawdb/accessors_chain.go↗ (Decisive callee branches: ReadHeader returns nil both when no header data is stored and when the stored header RLP fails to decode.):func ReadHeader(db ethdb.Reader, hash common.Hash, number uint64) *types.Header { data := ReadHeaderRLP(db, hash, number) if len(data) == 0 { return nil } header := new(types.Header) if err := rlp.DecodeBytes(data, header); err != nil { log.Error("Invalid block header RLP", "hash", hash, "err", err) return nil } return header }
cmd/geth/dbcmd.go↗ (The immediately preceding lookup establishes the error-return contract for missing data; the header lookup skips the equivalent check.):hash = rawdb.ReadCanonicalHash(db, number) if hash == (common.Hash{}) { return fmt.Errorf("canonical hash for block %d not found", number) }
Suggested fix: blockHeader := rawdb.ReadHeader(db, hash, number)
if blockHeader == nil {
return fmt.Errorf("header for block %d (%s) not found", number, hash)
}
trieRoot = blockHeader.RootSuggested test: Unit/CLI test: seed a test chaindb with WriteCanonicalHash for block N but no header (or a corrupt header RLP), invoke inspectTrie with argument N, and assert it returns a non-nil error rather than panicking.
-
⚪ Low: Re-idling the peer in revert*Request allows a tight assign/send-fail/revert loop on write errors (
eth/protocols/snap/sync.go:1832)
Impact: When a peer's request write fails synchronously (broken/closed connection that has not yet triggered Unregister), the request goroutine calls scheduleRevertAccountRequest immediately. With the new re-idle, revertAccountRequest puts the peer back into s.accountIdlers while it is still present in s.peers, and the next assignAccountTasks pass hands it the same task again, which fails again instantly. This spins assign -> send error -> revert -> re-idle (one goroutine plus one timer plus debug log lines per iteration) until the peer is unregistered. Before the change the peer stayed out of the idle pool, so a failing peer was used at most once per task.
During the window between a peer's write failure and its unregistration, the snap sync event loop can churn through many pointless assign/revert cycles, burning CPU, spamming debug logs and delaying assignment of that task to healthy peers. The timeout path is paced by rates.TargetTimeout() and is far less severe, but it likewise lets a black-hole peer keep re-acquiring tasks.Evidence:
Changed code:
// Remove the request from the tracked set and restore the peer to the // idle pool so it can be reassigned work (skip if peer already left). s.lock.Lock() delete(s.accountReqs, req.id) if _, ok := s.peers[req.peer]; ok { s.accountIdlers[req.peer] = struct{}{} } s.lock.Unlock()
eth/protocols/snap/sync.go↗ (assignAccountTasks (1056-1162) reverts immediately, with no delay/backoff, when the write to the peer fails; each attempt allocates a goroutine and a timer.):req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() { ... s.scheduleRevertAccountRequest(req) }) s.accountReqs[reqid] = req delete(s.accountIdlers, idle) s.pend.Add(1) go func(root common.Hash) { defer s.pend.Done() ... if err := peer.RequestAccountRange(reqid, root, req.origin, req.limit, uint64(cap)); err != nil { peer.Log().Debug("Failed to request account range", "err", err) s.scheduleRevertAccountRequest(req) } }(s.root)
eth/protocols/snap/sync.go↗ (Event loop (~758-790) re-runs assignment on every iteration, so a peer re-idled by the revert is reassigned the same task immediately.):case req := <-accountReqFails: s.revertAccountRequest(req) // loop top re-runs: s.assignAccountTasks(accountResps, accountReqFails, cancel)
eth/protocols/snap/sync.go↗ (The only filter on idle peers is statelessPeers; a peer whose write failed is not marked stateless, so nothing prevents immediate reselection.):for id := range s.accountIdlers { if _, ok := s.statelessPeers[id]; ok { continue } idlers.ids = append(idlers.ids, id) ... }
eth/protocols/snap/peer.go↗ (The send error is a p2p write error on a broken connection, i.e. persistent rather than transient, so the retry fails again instantly.):func (p *Peer) RequestAccountRange(...) error { ... return p2p.Send(p.rw, GetAccountRangeMsg, &GetAccountRangePacket{...}) }
Suggested fix: Do not re-idle on the synchronous send-failure path, or add a marker/backoff so a peer whose request send failed is not reselected immediately (e.g. mark it stateless/skip until a successful delivery, or signal via s.update instead of re-idling unconditionally). Restrict the re-idle to the timeout path the commit body describes.
Suggested test: In eth/protocols/snap/sync_test.go, register a peer whose RequestAccountRange always returns an error and assert that the number of request attempts made to that peer stays bounded (e.g. a small constant) while sync progresses via other peers.
-
⚪ Low: revertBytecodeRequest re-idles a peer whose response is still outstanding after a timeout (
eth/protocols/snap/sync.go:1878)
Impact: On the timeout revert path the peer is still present in s.peers and still owes a response, so the new unconditional re-add puts it back into s.bytecodeIdlers immediately. assignBytecodeTasks can then dispatch a second bytecode request to a peer that already failed to answer within TargetTimeout, leaving two outstanding requests to one stalled peer and allowing repeated re-issuance to the same slow peer instead of preferring healthy idlers. The guardif _, ok := s.peers[req.peer]only filters the disconnect case (Unregister deletes from s.peers before peerDrop triggers revertRequests).
Snap sync scheduling quality depends on not immediately handing new work to peers that just timed out. The re-idle creates a timeout/reassign churn loop against the same slow peer and doubles its outstanding request count, which can slow state sync progress on peers with degraded links.Evidence:
Changed code:
// Remove the request from the tracked set and restore the peer to the // idle pool so it can be reassigned work (skip if peer already left). s.lock.Lock() delete(s.bytecodeReqs, req.id) if _, ok := s.peers[req.peer]; ok { s.bytecodeIdlers[req.peer] = struct{}{} } s.lock.Unlock()
eth/protocols/snap/sync.go↗ (assignBytecodeTasks (1165-1278) removes the peer from bytecodeIdlers on dispatch and reverts on timeout while the peer is still registered in s.peers, so the new guard does not filter the timeout case; the peer becomes assignable again with a response still outstanding. It also decays the peer's msgrate capacity, which partially mitigates re-preferring the stalled peer.):req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() { peer.Log().Debug("Bytecode request timed out", "reqid", reqid) s.rates.Update(idle, ByteCodesMsg, 0, 0) s.scheduleRevertBytecodeRequest(req) }) s.bytecodeReqs[reqid] = req delete(s.bytecodeIdlers, idle)
eth/protocols/snap/sync.go↗ (onByteCodes (2827-2932) is the pre-existing idle-restore chokepoint and restores the peer even for a stale/late delivery, so the peer was not permanently excluded before this change; it also discards the stale delivery, bounding the impact of the duplicate outstanding request to scheduling churn rather than data corruption.):defer func() { s.lock.Lock() defer s.lock.Unlock() if _, ok := s.peers[peer.ID()]; ok { s.bytecodeIdlers[peer.ID()] = struct{}{} } ... }() ... req, ok := s.bytecodeReqs[id] if !ok { logger.Warn("Unexpected bytecode packet") ...
Suggested fix: If immediate re-idling is intended, confirm it explicitly (it changes upstream scheduling behavior for timed-out peers). Otherwise, restrict the re-add to revert reasons where the peer is known to be free (e.g. RequestByteCodes send failure / cancellation) and leave a timed-out peer out of the idle pool until its response arrives (onByteCodes already re-idles it, even for stale ids) or it is dropped.
Suggested test: Add a syncer test that dispatches a bytecode request, fires the timeout without any response, and asserts whether the peer reappears in s.bytecodeIdlers and is immediately handed a second bytecode request while the first is still outstanding; mirror it for the send-failure revert path to pin the intended distinction.
-
⚪ Low: AmsterdamBlock is wired into Rules but omitted from CheckConfigForkOrder and checkCompatible (
params/config.go:1938)
Impact: This hunk makes Rules.IsAmsterdam live, derived solely from ChainConfig.AmsterdamBlock. Because amsterdamBlock appears in neither CheckConfigForkOrder's fork list nor checkCompatible: (a) a custom genesis that sets amsterdamBlock earlier than osakaBlock, or sets it while osakaBlock/pragueBlock are nil, passes fork-order validation and yields Rules{IsAmsterdam:true, IsOsaka:false} — an out-of-order fork state the ordering chokepoint exists to reject; and (b) changing or removing an already-passed amsterdamBlock across a restart produces no ConfigCompatError, so the node starts with silently different Rules instead of refusing to start and demanding a rewind.
CheckConfigForkOrder and checkCompatible are the chokepoints that stop nonsensical or silently-mutated fork schedules from reaching Rules and the EVM. Every sibling gate (Cancun, Prague, Osaka, Verkle) is registered in both; Amsterdam is registered in neither, so the new gate is the one fork that can be misordered or retroactively rescheduled without any diagnostic. Impact is bounded today: AmsterdamBlock is nil on every bundled preset, so only operator-authored custom genesis files are affected, and no consumer of Rules.IsAmsterdam was confirmed in this tree — the risk grows as soon as Amsterdam semantics are attached to the flag.Evidence:
Changed code:
IsOsaka: c.IsOsaka(num), IsAmsterdam: c.IsAmsterdam(num), IsEIP4762: c.IsVerkle(num),
params/config.go↗ (Lines 1468-1470: the gate requires only London, not Osaka/Prague, so ordering sanity depends entirely on CheckConfigForkOrder — the check that is missing.):func (c *ChainConfig) IsAmsterdam(num *big.Int) bool { return c.IsLondon(num) && isBlockForked(c.AmsterdamBlock, num) }
params/config.go↗ (CheckConfigForkOrder (1521-1614) fork slice terminates at verkleBlock; amsterdamBlock is absent, so amsterdamBlock set with osakaBlock nil, or amsterdamBlock < osakaBlock, passes validation.):{name: "cancunBlock", block: c.CancunBlock, optional: true}, {name: "pragueBlock", block: c.PragueBlock, optional: true}, {name: "osakaBlock", block: c.OsakaBlock, optional: true}, {name: "verkleBlock", block: c.VerkleBlock, optional: true}, } {params/config.go↗ (checkCompatible (1629-1721) ends here with no AmsterdamBlock clause, so rescheduling/removing an already-passed amsterdamBlock returns nil instead of a ConfigCompatError.):if isForkBlockIncompatible(c.OsakaBlock, newcfg.OsakaBlock, headNumber) { return newBlockCompatError("Osaka fork block", c.OsakaBlock, newcfg.OsakaBlock) } return nil }
params/config.go↗ (Line 857: the field is JSON-settable, so operator-supplied custom genesis files can reach the unvalidated path.):AmsterdamBlock *big.Int `json:"amsterdamBlock,omitempty"` // Amsterdam switch Block (nil = no fork, 0 = already on amsterdam)
Suggested fix: Add
{name: "amsterdamBlock", block: c.AmsterdamBlock, optional: true}to the CheckConfigForkOrder fork slice, positioned consistently with the Osaka/Verkle entries, and add to checkCompatible:if isForkBlockIncompatible(c.AmsterdamBlock, newcfg.AmsterdamBlock, headNumber) {
return newBlockCompatError("Amsterdam fork block", c.AmsterdamBlock, newcfg.AmsterdamBlock)
}mirroring the existing Osaka handling.
Suggested test: In params/config_test.go add a CheckConfigForkOrder case with AmsterdamBlock=1 and OsakaBlock=nil (and one with AmsterdamBlock < OsakaBlock) expecting an ordering error, plus a CheckCompatible case rescheduling AmsterdamBlock past an already-forked head expecting a ConfigCompatError.
🙋 Needs human attention:
- Does lowering testBlockChain basefee 1050→1 and blobfee 105→1 in TestAdd still exercise the pending-vs-queued/underpriced path (e.g. the gapped-nonce TxStatusQueued check and the ErrTxGasPriceTooLow sub-case), or does it now make every seeded tx trivially executable and weaken those assertions?
- Does muxTracer.OnTxStart/OnTxEnd/OnOpcode nil-check each sub-tracer's individual hook functions before invoking them (the fileWritingTracer hooks set every hook, so this is likely fine, but the opcode counter Tracer only defines OnOpcode)?
- Is the new stateDB!=nil precompile touch path (stateDB.Exist(address) in RunPrecompiledContract) exercised by any test, or only by production EVM callers in core/vm/evm.go?
- Do the generated files core/types/gen_header_json.go and core/types/gen_header_rlp.go include the new Header.SlotNumber optional field, and does CopyHeader in core/types/block.go deep-copy it?
- Does trie/proof_test.go (TestRangeProofWithInvalidNonExistentProof, TestOneElementRangeProof) still expect the old error/success behavior for cases where firstKey is greater than keys[0], now that VerifyRangeProof short-circuits with "unexpected key-value pairs preceding the requested range"?
- Additional unresolved notes suppressed: 12
Sorry, this review is incomplete. The allotted max token limit of 8000000 (config
review.maxBudgetTokens) was reached and the review has been degraded. Raise the limit for a complete review.
— codegenie v0.5.5 (58f82a9b2c) · View Workflow Job
There was a problem hiding this comment.
Beyond the inline fork-order/compat finding, I also checked whether Amsterdam needs its own case in activePrecompiledContracts/ActivePrecompiles (core/vm/contracts.go) — it doesn't add a distinct precompile set versus Osaka (only SLOTNUM + BAL touch instrumentation), so falling through to the IsOsaka case is correct as long as Osaka activates at or before Amsterdam, which is the same ordering guarantee the inline finding shows is currently unenforced.
Extended reasoning...
Verified by reading core/vm/contracts.go: neither activePrecompiledContracts nor ActivePrecompiles has an IsAmsterdam case, and both fall through to IsOsaka. This is fine on its own since Amsterdam (EIP-7843 SLOTNUM, EIP-8024, BAL precompile-touch) doesn't introduce a new precompile, but it means correctness here is coupled to Osaka activating no later than Amsterdam in config — the exact invariant the inline CheckConfigForkOrder/checkCompatible gap leaves unchecked. Not a new/independent bug, so not filed separately, but worth recording as examined.
…develop-drift cascade)
…evelop-drift cascade) Brings the base up to date after milestone 2 (#2319) merged, carrying 10 commits of develop drift on top of it — WIT2 BP-signed witness announcements (#2208), the live-tracer system-transaction fix (#2353), V2 sender-nonce precompute (#2383) and dependency bumps. No conflicts. Milestone 2's content reached this branch through its own history rather than through the merge, so the only new material is develop's, and it does not overlap the v1.17.1 work. Build, vet and the witness, state, VM, fork and consensus/bor suites are green.
Bor schedules Amsterdam by block where upstream schedules it by timestamp. The conversion carried the AmsterdamBlock field, IsAmsterdam and the Rules entry, but not the three other surfaces upstream wires for AmsterdamTime: the ordered fork list in CheckConfigForkOrder, the incompatibility check in checkCompatible, and the startup banner. Without them a genesis could schedule amsterdamBlock ahead of osakaBlock and pass validation, and moving amsterdamBlock under an already-synced head would return no ConfigCompatError and trigger no rewind, applying different consensus rules to blocks already imported. Both are latent while the block is nil on every preset and become reachable as soon as any config sets it. TestAmsterdamForkGuards covers all three; each subtest fails without the corresponding guard. ChainConfig.Block(forks.Amsterdam) is left alone deliberately — the method has no callers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
quick question on this one — the touch we added for self destructed accounts ( looks like this bit came straight from upstream (the TODO comment about extending the reader with im wondering if we should keep with this check since its amsterdam, or if we should remove/gate it until the fork actually gets scheduled, just to be consistent with how we gated the other touch and the "no behavior change" claim in the description. |
Upstream reads a destructed account's storage slot in GetCommittedState purely to record the access for the block-level access list, and does so unconditionally. The value is discarded and the state root is unchanged, so it looks behaviour-preserving, but it is not preserving for the witness. NewTrieOnly forces reads through the MPT precisely so the witness captures the nodes walked, so this read pulls the destructed account's storage proof path into the block witness. A node running this version against a witness produced by one that did not would find those nodes missing, the reader would error, setError would record it, and the commit would abort — the block fails to import. It is cheap to reach: create and self-destruct in one transaction, then read that address's storage later in the same block. That is the producer/consumer skew the Hampi gate exists for, and with most validators running stateless it is a liveness concern rather than a cosmetic one. The block-level access list is the only consumer of the read and does not exist before the fork, so gate it the way every other Amsterdam surface in this milestone is gated. StateDB gains an amsterdam flag set from rules.IsAmsterdam in Prepare, which every message execution runs through, and carried through Copy. Prepare is per-transaction, so a path reaching GetCommittedState without preparing one leaves the flag false and skips the read. That matches pre-Amsterdam behaviour and is correct while the fork is dormant, but it has to be revisited when Amsterdam is scheduled. Raised by Lucca Martins in review of #2325. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Good catch — and I think the reason to gate it is stronger than the one you landed on. Everything you describe checks out: the read is ungated, the precompile touch in The wasted disk read is real but small — post-EIP-6780 That gives the asymmetry we already know is dangerous:
And it's cheap to reach: create-and-selfdestruct in one tx, then read that address's storage in a later tx of the same block. With most mainnet validators running stateless and gating milestone voting, a mixed-version window on that is exactly what we built Hampi for. There's a policy angle too — Fixed in Deliberate divergence from upstream, which doesn't gate it — recorded as such so a future sync doesn't quietly drop it. One caveat I put in the commit message and the ledger rather than hiding: Separately, your question is the second time we've reasoned our way through witness skew on a whiteboard with no test that could have caught it (PIP-88/Hampi was the first). We're going to look at wiring old-producer/new-consumer coverage into the kurtosis/e2e setup so this stops being a review-time catch. |
TestDestructedSlotReadIsAmsterdamGated drove the gate from a hand-built params.Rules, which exercised the plumbing downstream of IsAmsterdam but never IsAmsterdam itself. Build the rules from a ChainConfig carrying a real AmsterdamBlock and assert no reader access at N-1 and exactly one at N and N+1, matching the boundary coverage hardfork-rollout.md asks for and the shape used for Hampi. Also record the first milestone-tier verification in the ledger. It passes -- 144 packages, no failures -- but only when run through the repo's own contract. A plain `go test ./...` reports four failures that are all artifacts: cmd/ is excluded from TESTALL, and core exceeds the 10m default timeout under parallel package execution while finishing in 175s with -p 1. The ledger entry spells this out so the next run does not repeat it, and notes the pre-existing unguarded w.chainConfig.Bor dereference in miner/worker.go that the excluded cmd/ tests expose. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The gate on the EIP-7928 destructed-account read was argued for from the code rather than measured. TestDestructedReadWitnessSkew measures it: one committed storage trie, one destructed-account read, the witness harvested both ways. With the read gated off the witness holds 1 node; ungated it holds 5, and the 4 extra are a clean superset. That is the shape that matters. Witness content is not committed to any header field, so a producer and a consumer disagreeing about it is invisible to consensus -- the consumer demands nodes the producer never recorded and fails the import. The test asserts both the difference and its direction, and fails if the gate is removed, so it cannot pass vacuously. The reader path is easy to misread. #2180's comment in updateTrie says reader reads are not in obj.trie, which is true, and which looks like it means reader reads never reach the witness, which is false -- they arrive via trieReader.CollectStateWitness, harvested by StateDB.CollectStateWitness on the V2 path. Recorded in the ledger alongside the measurement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every batch entry carried a "pre-existing failures" line naming the same three items. Re-checked at 6b4820b against the repo's test contract: none of them is currently a known-red. TestPDBMethodParity passes, so the standing pdbExemptMethods TODO is stale. The two core/vm interrupt tests are not skipped by -short and ran 20/20 green; their recorded flakiness predates the cascade that pulled in #2371, "fix pipeline race test failures", which is a plausible but unconfirmed cause. The cmd/evm t8n entries were never in the suite at all -- cmd/ is excluded from TESTALL -- so they were recorded as accepted breakage from runs made outside the contract. The t8n golden drift is real if those packages are run directly. It is simply not something this sync gates on, and carrying it as known-red teaches reviewers to expect red where the suite is green, which is how a real failure gets waved through. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lucca30
left a comment
There was a problem hiding this comment.
Verified the fix for the destructed-account read gating (4570596, 343241f, 6b4820b): built core/state and ran TestDestructedSlotReadIsAmsterdamGated + TestDestructedReadWitnessSkew locally against this branch, both pass and confirm the witness-skew claim (1 node gated-off vs 5 gated-on). Good catch on the producer/consumer skew, and the boundary test + measured witness test go beyond what was asked.
No other blockers from my side. Approving.
…able The Amsterdam gate added in 4570596 sits on the stateObjectsDestruct branch, but in Bor that branch is not the one this block's own destructs take: currentBlockDestructs intercepts those above and returns without any read. What reaches the gate are the parent block's destructs, seeded by ApplyFlatDiff on the pipelined-SRC path -- the witness-producing path, which is what makes the skew matter rather than being incidental. captureReadOnlyAccount skips destructed accounts, so the witness has never carried these nodes and nothing depends on them being there. Comment only, no behaviour change. Raised in review by @cffls. Gating the block-level access list and the EIP-7843 slot number independently of IsAmsterdam is tracked separately as POS-3715. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dafc6da
into
upstream-merge-v1.17.4
Important
Reviewer guide — stacked PR 3 of 12. Part of the combined go-ethereum v1.17.4 + v1.17.5 upstream sync, which ships as one stable release. Every PR in the stack merges into the base branch
upstream-merge-v1.17.4; that base merges intodeveloponce, at the very end — not per-PR.Merge-commit only — never squash. Squashing rewrites a branch's SHAs and breaks every PR stacked above it.
Review bottom-up: #2308 → #2319 → #2325 → #2328 → #2337 → #2340 → #2341 → #2342 → #2343 → #2345 → #2346 → #2354. Start at #2308 / #2319 — every PR above inherits them, so reviewing top-down means re-reviewing.
Expected-red / flaky checks (not code blockers):
Quality metrics(diffguard — skipped by team decision; it also mis-scopes across a stacked diff, comparing against the bottom of the stack), andcodecov/project(repo-wide coverage threshold; per-PR patch coverage is green). Kurtosis e2e occasionally flakes (~1-in-5, devtools-owned) and is re-run by hand. Full per-batch conflict-resolution reasoning is indocs/upstream-merges/.Summary
Milestone 3/6 of the Bor ← go-ethereum v1.17.4 sync: merges upstream
v1.17.0 → v1.17.1(40 first-parent commits) across 2 batches, plus the milestone-chores docs.Introduces the Amsterdam fork surface (EIP-7843 SLOTNUM, EIP-8024, BAL precompile-touch) — wired block-based-nil like Osaka/Prague/Verkle, dormant on every Bor network. Enabling later is just setting
AmsterdamBlock(no core-logic changes). This also corrects a v1.17.0 gap where upstream's Amsterdam gate had been dropped rather than converted to Bor's block-based style.Stacked on #2319 (v1.17.0). Base retargets up-chain as lower PRs merge + their branches are deleted.
9ecb6c4ae18903086616783c167b6175113d(incl. Amsterdam gate wiring)dbae0f4a1(docs)Executed tests
go build ./...rc=0;go vetclean (bar pre-existing//nolintcopylocks).go test ./...— no v1.17.1 regressions. Two failures confirmed pre-existing/flaky:cmd/gethTestCustomBackend(VEBLOP/non-Bor-genesis nil-deref, crash path byte-identical to v1.17.0) andconsensus/bor/heimdallTestFailover_ThreeClients_CascadeToTertiary(flaky, passes 5/5 on re-run).make test-integration(-tags integration -p 1 ./tests/...) — PASS, incl.tests/borconsensus e2e (620s, 77.6% cov, GitCommitb6175113d).govulncheck— 3 called vulns, all pre-existing/develop-inherent (x/text, x/crypto, crypto/tls); c-kzg v2.1.6 added none.TestReinforceMultiClientPreCompilesTest+TestV2ForkParity+TestBorHardforkPrecompileContinuity*.tests/boris green.Rollout notes
AmsterdamBlockis nil on every preset soIsAmsterdamis false everywhere.docs/upstream-merges/v1.17.4/needs-wiring.md): eth/68 drop (#33511, entangled with Bor's forked downloader/wit/state-sync-receipt/PoA propagation), on-chain-tx-check (#33607), testing_buildBlockV1 (#33656).v2.1.5→v2.1.6, olekukonko→internal/tablewritermigration.🤖 Generated with Claude Code