core, eth, trie: merge geth v1.17.0 (v1.17.4 sync, milestone 2/6) - #2319
core, eth, trie: merge geth v1.17.0 (v1.17.4 sync, milestone 2/6)#2319pratikspatil024 wants to merge 245 commits into
Conversation
Because the map iteration is unstable, we need to order logs by tx index and keep the same order with receipts and their logs, so we can still get the same `LogsHash` across runs. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: rjl493456442 <garyrong0905@gmail.com>
The version check incorrectly used `&&` instead of `||`, causing versions like v1.0.x through v1.4.x to be allowed when they should be rejected. These versions don't support EIP-712 signing which was introduced in firmware v1.5.0.
Removes an unnecessary todo. This case is handled, the comment was an artifact from Kev's refactor
…r (#33098)
failed in 32bit:
```
--- FAIL: TestDecodeSingleCorruptedData (0.00s)
panic: runtime error: slice bounds out of range [:-1501805520] [recovered, repanicked]
goroutine 38872 [running]:
testing.tRunner.func1.2({0x838db20, 0xa355620})
/opt/actions-runner/_work/_tool/go/1.25.3/x64/src/testing/testing.go:1872 +0x29b
testing.tRunner.func1()
/opt/actions-runner/_work/_tool/go/1.25.3/x64/src/testing/testing.go:1875 +0x414
panic({0x838db20, 0xa355620})
/opt/actions-runner/_work/_tool/go/1.25.3/x64/src/runtime/panic.go:783 +0x103
github.com/ethereum/go-ethereum/triedb/pathdb.decodeSingle({0x9e57500, 0x1432, 0x1432}, 0x0)
/opt/actions-runner/_work/go-ethereum/go-ethereum/triedb/pathdb/history_trienode.go:399 +0x18d6
github.com/ethereum/go-ethereum/triedb/pathdb.TestDecodeSingleCorruptedData(0xa2db9e8)
/opt/actions-runner/_work/go-ethereum/go-ethereum/triedb/pathdb/history_trienode_test.go:698 +0x180
testing.tRunner(0xa2db9e8, 0x83c86e8)
/opt/actions-runner/_work/_tool/go/1.25.3/x64/src/testing/testing.go:1934 +0x114
created by testing.(*T).Run in goroutine 1
/opt/actions-runner/_work/_tool/go/1.25.3/x64/src/testing/testing.go:1997 +0x4b4
FAIL github.com/ethereum/go-ethereum/triedb/pathdb 41.453s
? github.com/ethereum/go-ethereum/version [no test files]
FAIL
```
Found in
https://github.com/ethereum/go-ethereum/actions/runs/18912701345/job/53990136071?pr=33052
- Replace hardcoded DigestLength - Add hash length validation
Rollups may want to use these to dynamically adjust blobs posted after BPO forks.
The periodic sealing loop failed to reset its timer when sealBlock returned an error, causing the timer to never fire again and effectively halting block production in developer periodic mode after the first failure. This is a bug because the loop relies on the timer to trigger subsequent sealing attempts, and transient errors (e.g., pool races or chain rewinds) should not permanently stop the loop. The change moves timer.Reset after the sealing attempt unconditionally, ensuring the loop continues ticking and retrying even when sealing fails, which matches how other periodic timers in the codebase behave and preserves forward progress.
[powdr](github.com/powdr-labs/powdr) has tested keeper in their womir system and managed to get it to work. This PR adds wasm as a keeper target. There's another plan by the zkevm team to support wasm with wasi as well, so these PR adds both targets. These currently uses the `example` tag, as there is no precompile intefrace defined for either target yet. Nonetheless, this is useful for testing these zkvms so it makes sense to support these experimental targets already.
While updating to latest Geth, I noticed `OnCodeChangeV2` was not properly handled in `SelfDestruct/6780`, this PR fixes this and bring a unit test. Let me know if it's deemed more approriate to merge the tests with the other one.
Context from Cloudflare blog: https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive We were able to reproduce the same issue discussed by Cloudflare in their recent blog post above using the `ethclient`.
Add BlobTxs flag to filter. Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com>
Add nil check before calling tx.Type() to prevent panic when transaction is not found.
The iterator loop in findTxInBlockBody returned the outer-scoped err when iter.Err() was non-nil, which could incorrectly propagate a nil or stale error and hide actual RLP decoding issues. This patch returns iter.Err() as intended by the rlp list iterator API, matching established patterns elsewhere in the codebase and improving diagnostics when encountering malformed transaction entries.
…3186) Co-authored-by: Gary Rong <garyrong0905@gmail.com>
…T, and move the transition tree to its own package (#32445) This is broken off of #31730 to only focus on testing networks that start with verkle at genesis. The PR has seen a lot of work since its creation, and it now targets creating and re-executing tests for a binary tree testnet without the transition (so it starts at genesis). The transition tree has been moved to its own package. It also replaces verkle with the binary tree for this specific application. --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com>
Show the actual gas used in the block limit error so RPC clients see useful numbers.
Fixes a potential panic in `randomDuration` when `min == max` by handling the edge case explicitly.
`StateDB` lacks recording functionality, so it has been replaced with `tractStateDB` and advanced
Regenerate gen_config.go for TrienodeHistory + NodeFullValueCheckpoint TOML marshaling (+ sync NoSnapServing); exempt DumpBinTrieLeaves in the StateDB/ParallelStateDB parity check; commit the v1.17.4-sync tracking docs.
Adapt TestBlockReceiptsPreservesCanonicalFlag to Bor's rpc.NewServer signature; regenerate eth_getTransactionBy* goldens for the adopted blockTimestamp field (state-sync-tx fixtures preserved).
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Codecov Report❌ Patch coverage is ❌ Your patch check has failed because the patch coverage (38.75%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## ppatil-upstream-v1.16.9 #2319 +/- ##
===========================================================
+ Coverage 54.21% 54.24% +0.02%
===========================================================
Files 908 912 +4
Lines 162245 164111 +1866
===========================================================
+ Hits 87966 89015 +1049
- Misses 68842 69540 +698
- Partials 5437 5556 +119
... and 32 files with indirect coverage changes
🚀 New features to boost your workflow:
|
The develop-drift cascade brought in #2333's prewalk and read-set tests, which call newTrieReader with the verkle point-cache argument develop still has. Upstream dropped that parameter within the v1.17.0 range, so the calls carry one argument too many from this branch upward and the core/state test package no longer typechecks. Same two-line adaptation the merge into ppatil-upstream-v1.17.2 already carried; this branch and the one above it were missed. The reason they were missed is worth recording, because the failure mode is easy to repeat: the cascade's lower hops were verified with go build, and go build does not compile test files. Nothing was wrong with the merge — it was clean and the production tree built — but a test package that cannot typecheck fails both the lint and unit-tests jobs, which is exactly what CI reported. go vet ./... catches this and go build ./... never will. Verified: build clean; full-tree go vet clean apart from the two pre-existing lock-copy findings in core/parallel_state_processor.go and trie/secure_trie.go; make lint reports 0 issues; all seven of #2333's prewalk and read-set tests pass.
|
codegenie review |
|
@claude review |
🧞 Codegenie ReviewWarning Review incomplete. Some review or verification work did not complete. Planning also fell back to deterministic default coverage. Treat the results below as partial and consider rerunning.
Review incomplete: completed work produced no credible verified findings, but incomplete coverage or verification prevents a clean conclusion. CoveragePartial review: 973 hunks were not reviewed because budget was exhausted before dispatch.
Stats
|
| // TODO: consider if parallelizing this is worth it, probably depending on len(ae.chunks). | ||
| keys := make([][]byte, 0, len(ae.chunks)) | ||
| for chunk := range ae.chunks { | ||
| basePoint := ae.pointCache.Get(chunk.addr[:]) | ||
| key := utils.GetTreeKeyWithEvaluatedAddress(basePoint, &chunk.treeIndex, chunk.leafKey) | ||
| var offset [32]byte | ||
| treeIndexBytes := chunk.treeIndex.Bytes32() | ||
| copy(offset[:31], treeIndexBytes[1:]) | ||
| offset[31] = chunk.leafKey | ||
| key := bintrie.GetBinaryTreeKey(chunk.addr, offset[:]) | ||
| keys = append(keys, key) | ||
| } | ||
| return keys | ||
| } |
There was a problem hiding this comment.
🟡 AccessEvents.Keys() (core/state/access_events.go:71-83, new in this PR) reconstructs the binary-trie key for a storage slot from bintrie.StorageIndex()'s (treeIndex, leafKey) pair by taking treeIndex.Bytes32()[1:] as the 31-byte tree-index. This does not match trie/bintrie.GetBinaryTreeKeyStorageSlot() (used by BinaryTrie.GetStorage/UpdateStorage/DeleteStorage to actually read/write the slot), which builds its tree-index via a naive byte-copy that drops slot byte 30 instead of shifting right by 8 bits. For essentially any real (keccak-derived) storage slot the two derivations diverge, so the witness/access-key set built by Keys() points at the wrong trie location. Fix by making GetBinaryTreeKeyStorageSlot use the same arithmetic right-shift-by-8 as StorageIndex (or vice versa, whichever matches the EIP-6800 spec) so both paths agree. Currently unreachable since the binary-trie/EIP-4762 fork is dormant on all Bor networks, so this does not block merge, but must be fixed before that fork could ever activate.
Extended reasoning...
What the bug is: AccessEvents.Keys() (core/state/access_events.go:71-83) is new code in this PR that reconstructs a binary-trie key from a chunkAccessKey (an (addr, treeIndex, leafKey) triple recorded by SlotGas/bintrie.StorageIndex()). It does this by taking chunk.treeIndex.Bytes32()[1:] as the 31-byte tree-index portion of the key offset and chunk.leafKey as the final suffix byte, then calling bintrie.GetBinaryTreeKey(addr, offset).
The problem is that this reconstruction does not match the key actually used to store and retrieve the same storage slot in the trie. trie/bintrie.GetBinaryTreeKeyStorageSlot() — the function BinaryTrie.GetStorage, UpdateStorage, and DeleteStorage (trie/bintrie/trie.go:239, 282, 305) actually call to touch storage — builds its 31-byte tree-index stem via a naive byte-copy: k[0]=1; copy(k[1:31], slot[0:30]); k[31]=slot[31]. This silently drops slot[30] and effectively computes tree_index = 2^240 + (slot >> 16).
bintrie.StorageIndex() (the function that feeds Keys() via SlotGas), on the other hand, does the spec-correct EIP-6800 arithmetic: tree_index = 2^240 + (slot >> 8), extracting the suffix as slot[31].
Why they diverge: the two derivations only agree when slot < 256 (i.e., when byte 30 and everything above it in slot >> 8 is zero and shifting by 8 vs 16 makes no observable difference). For any storage slot at or above 256 — which covers virtually every real Solidity mapping/array slot, since those are keccak256 outputs — the two functions produce completely different 32-byte trie keys. This was verified concretely by multiple independent checks: e.g. for slot 0x10000, the direct GetBinaryTreeKeyStorageSlot key and the Keys()-style reconstruction via StorageIndex differ entirely, and one verifier found the exact off-by-one-byte signature (the storage key for slot 0x10000 equals the witness key for slot 0x100).
Why nothing currently catches this: AccessEvents was switched in this PR from the old verkle-tree utils.StorageIndex/PointCache derivation (which was presumably consistent with the verkle trie's own key derivation) to the new bintrie package. The pre-existing GetBinaryTreeKeyStorageSlot function was not updated to match, and no test in this PR exercises Keys() against the trie's actual storage locations for a realistic (large) slot value — the only test that exists is a narrowly-scoped unit test, and slots below 256 happen to accidentally agree.
Impact: Under EIP-4762/binary-trie execution, AccessEvents.Keys() is used to build the state-access witness key set (used for gas metering and stateless-witness verification). With this bug, it would report a bogus key for essentially every non-trivial storage slot access instead of the slot's real trie location, corrupting witness generation for any block that touches ordinary contract storage.
Concrete proof (byte-level walkthrough) for slot = 0x10000:
StorageIndex(0x10000)computestreeIndex = 2^240 + (0x10000 >> 8) = 2^240 + 0x100. As a 32-byte big-endian value, this has0x01at byte index 29 (counting from 0), matching0x100sitting two bytes from the end.Keys()takestreeIndex.Bytes32()[1:](bytes 1..31, i.e. effectively re-indexing bytes 1..30 of the original 32-byte value onto offset[0..29]) — the0x01lands at offset index 29, hash input becomes[..., 0x01, 0x00]with leafKey (slot[31]=0x00) appended as offset[31].GetBinaryTreeKeyStorageSlot(addr, 0x10000)instead copiesslot[0:30]directly intok[1:31]and overwritesk[31]=slot[31]. For slot0x10000(32-byte big-endian: zeros except byte 29 =0x01), this puts0x01at byte index 30 of the key offset (not 29), because it never shifts — it's a literal byte copy of the original slot bytes.- The two 32-byte
offsetvalues passed intoGetBinaryTreeKey(addr, offset)therefore differ (0x01 at position 29 vs. position 30), producing two entirely different sha256-derived 32-byte trie keys.
Fix: Align the two derivations — either make GetBinaryTreeKeyStorageSlot perform the same slot >> 8 arithmetic shift that StorageIndex uses (the EIP-6800-correct approach), or make Keys()/StorageIndex mirror whatever GetBinaryTreeKeyStorageSlot actually does. Either way, the witness-key derivation path and the actual trie storage/read path must agree bit-for-bit.
| func opDupN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { | ||
| code := scope.Contract.Code | ||
| i := *pc + 1 | ||
|
|
||
| // If the immediate byte is missing, treat as 0x00 (same convention as PUSHn). | ||
| var x byte | ||
| if i < uint64(len(code)) { | ||
| x = code[i] | ||
| } | ||
|
|
||
| // This range is excluded to preserve compatibility with existing opcodes. | ||
| if x > 90 && x < 128 { | ||
| return nil, &ErrInvalidOpCode{opcode: OpCode(x)} | ||
| } | ||
| n := decodeSingle(x) | ||
|
|
||
| // DUPN duplicates the n'th stack item, so the stack must contain at least n elements. | ||
| if scope.Stack.len() < n { | ||
| return nil, &ErrStackUnderflow{stackLen: scope.Stack.len(), required: n} | ||
| } | ||
|
|
||
| //The n‘th stack item is duplicated at the top of the stack. | ||
| scope.Stack.push(scope.Stack.Back(n - 1)) | ||
| *pc += 1 | ||
| return nil, nil | ||
| } |
There was a problem hiding this comment.
🟡 opDupN, opSwapN, and opExchange (core/vm/instructions.go, added for EIP-8024) each re-implement the identical snippet for reading the opcode's immediate byte with an implicit-zero default (code := scope.Contract.Code; i := *pc + 1; var x byte; if i < uint64(len(code)) { x = code[i] }), including the same explanatory comment. Extracting this into a small helper (e.g. immediateByte(pc *uint64, scope *ScopeContext) byte) would remove the triplication and keep the PUSHn-style boundary convention in one place for future fixes.
Extended reasoning...
What the duplication looks like
core/vm/instructions.go adds three new opcode handlers for EIP-8024: opDupN (~lines 1025-1050), opSwapN (~lines 1052-1079), and opExchange (~lines 1081-1111). Each begins with the byte-for-byte identical block:
code := scope.Contract.Code
i := *pc + 1
// If the immediate byte is missing, treat as 0x00 (same convention as PUSHn).
var x byte
if i < uint64(len(code)) {
x = code[i]
}This is the "read the immediate operand byte following the opcode, defaulting to 0x00 if the code buffer runs out" convention borrowed from the existing PUSHn opcodes, but here it's freshly copy-pasted three times in the same PR rather than factored into a shared helper.
Why this is worth flagging
Each of the three handlers also carries its own opcode-specific range check (x > 90 && x < 128 for DUPN/SWAPN vs x > 79 && x < 128 for EXCHANGE) and its own *pc += 1 increment, which legitimately differ per-opcode and are not part of this finding. Only the immediate-byte-read snippet itself — five identical lines plus an identical comment — is duplicated three times. Because the read logic and its boundary convention now exist in three copies, any future correction to that convention (e.g. a boundary/off-by-one fix, or a change in how out-of-bounds immediates are handled) has to be applied identically in three places, and it's easy to update one copy and miss the others.
Suggested fix
Extract the read into a small helper, e.g.:
// immediateByte returns the opcode's immediate byte at pc+1, or 0x00 if the
// code buffer doesn't extend that far (same convention as PUSHn).
func immediateByte(pc *uint64, scope *ScopeContext) byte {
code := scope.Contract.Code
i := *pc + 1
if i < uint64(len(code)) {
return code[i]
}
return 0
}and call x := immediateByte(pc, scope) at the top of each of opDupN, opSwapN, and opExchange, leaving each opcode's own range check and *pc += 1 untouched. This is a pure refactor — the read logic and its behavior are unchanged, only its location — so there's no risk of altering opcode semantics. Since this is a tiny, inlinable helper on a hot execution path (the EVM interpreter loop), there's no meaningful performance concern either; the Go compiler will happily inline it.
Step-by-step verification
- Open
core/vm/instructions.goand locateopDupN: it containscode := scope.Contract.Code; i := *pc + 1; var x byte; if i < uint64(len(code)) { x = code[i] }with the comment "If the immediate byte is missing, treat as 0x00 (same convention as PUSHn)." - Locate
opSwapNimmediately below it: the identical five-line block and identical comment appear again, verbatim. - Locate
opExchangeimmediately below that: the same five-line block and comment appear a third time. - Diff the three blocks against each other — they are character-for-character identical, confirming genuine triplication rather than superficially similar code.
- Confirm the surrounding code (range checks,
*pc += 1) differs per-opcode, so only the immediate-byte-read logic itself is the duplicated unit, and it is exactly what a singleimmediateBytehelper would replace.
This is scoped purely to code quality — reuse and maintainability — with no functional or security implication, so it should not block merging this PR.
| addStaleMeter.Mark(1) | ||
| case errors.Is(err, core.ErrNonceTooHigh): | ||
| addGappedMeter.Mark(1) | ||
| // Store the tx in memory, and revalidate later | ||
| from, _ := types.Sender(p.signer, tx) | ||
| allowance := p.gappedAllowance(from) | ||
| if allowance >= 1 && len(p.gapped) < maxGapped { | ||
| p.gapped[from] = append(p.gapped[from], tx) | ||
| p.gappedSource[tx.Hash()] = from | ||
| log.Trace("added tx to gapped blob queue", "allowance", allowance, "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "qlen", len(p.gapped[from])) | ||
| return nil | ||
| } else { | ||
| // if maxGapped is reached, it is better to give time to gapped | ||
| // transactions by keeping the old and dropping this one. | ||
| // Thus replacing a gapped transaction with another gapped transaction | ||
| // is discouraged. | ||
| log.Trace("no gapped blob queue allowance", "allowance", allowance, "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "qlen", len(p.gapped[from])) | ||
| } | ||
| case errors.Is(err, core.ErrInsufficientFunds): | ||
| addOverdraftedMeter.Mark(1) | ||
| case errors.Is(err, txpool.ErrAccountLimitExceeded): |
There was a problem hiding this comment.
🟡 The gapped-blob admission check at blobpool.go:1532 (len(p.gapped) < maxGapped) caps the number of distinct sender addresses with a buffered gapped tx, not the total number of buffered transactions as the maxGapped doc comment promises (len(p.gappedSource) is the true total and is never checked). Since gappedAllowance() grants up to maxTxsPerAccount (16) gapped slots per high-nonce address, an attacker with up to 128 funded, elevated-nonce accounts can buffer well beyond the intended 128 total gapped blob transactions in memory (each holding a full blob sidecar) for up to gappedLifetime (1 minute). Fix by checking len(p.gappedSource) < maxGapped instead of len(p.gapped) < maxGapped.
Extended reasoning...
The bug: In BlobPool.addLocked() (core/txpool/blobpool/blobpool.go:1526-1546), when a blob transaction fails validation with core.ErrNonceTooHigh, it is buffered into the in-memory reorder queue p.gapped[from] under the guard:
if allowance >= 1 && len(p.gapped) < maxGapped {
p.gapped[from] = append(p.gapped[from], tx)
p.gappedSource[tx.Hash()] = from
...
}p.gapped is declared as map[common.Address][]*types.Transaction, so len(p.gapped) counts the number of distinct sender addresses that currently have at least one gapped transaction — not the total number of buffered transactions. The pool also maintains p.gappedSource map[common.Hash]common.Address, which is incremented once per transaction and therefore holds the true total count, but this map is never consulted by the admission check.
Why this bypasses the intended limit: The constant's own doc comment states: maxGappedTxs is the maximum number of gapped transactions kept overall. This is a safety limit to avoid DoS vectors. The code does not enforce that invariant — it enforces a cap on distinct addresses instead. Per-address, gappedAllowance() (blobpool.go:1977 area) computes min(int(math.Log10(nonce+1)), maxTxsPerAccount-len(index[addr])) - len(gapped[addr]), where maxTxsPerAccount is 16. This means an address with a sufficiently high on-chain nonce (e.g. nonce ≥ 1000 gives allowance 3, nonce ≥ 100000 gives allowance 5, up to a cap of 16) can have multiple gapped transactions queued simultaneously, each retaining its full blob sidecar in memory (up to ~768KB for 6 blobs) since blob sidecars are only stripped after successful pool insertion via WithoutBlobTxSidecar().
Step-by-step proof:
- Attacker controls 127 funded accounts (just under
maxGapped= 128), each with an on-chain nonce ≥ 100000 (cheap to reach on Bor given low gas fees and high throughput). - For each account,
gappedAllowance()returns up to 5 (or higher for higher nonces, capped at 16). - Attacker sends 5 nonce-gapped blob transactions per account (skipping the immediate next nonce so they hit
ErrNonceTooHigh). - Each insertion checks
len(p.gapped) < 128— sincep.gappedonly grows by one new key per first gapped tx from a given address, all 5 txs per account are accepted under the same address-count budget. - Total buffered blob transactions: 127 × 5 = 635, or up to 127 × 16 ≈ 2032 in the worst case — far beyond the documented cap of 128 — each holding a full blob sidecar in memory for up to
gappedLifetime(1 minute).
Why existing code doesn't prevent it: The check only reads len(p.gapped), never len(p.gappedSource), so nothing in the admission path actually bounds the total transaction count; it only bounds the number of addresses participating in the gapped-reorder buffer.
Impact: This is a memory-amplification gap relative to the documented safety limit — an externally-triggerable path (blob txs arrive from the p2p network) that lets an attacker with a moderate number of funded, high-nonce accounts inflate blobpool memory usage well past the intended 128-transaction bound, though it is still bounded (by address count × per-account cap) and self-heals after the 1-minute gappedLifetime eviction window, and requires funding many accounts with elevated nonces to become meaningful.
Fix: Change the guard at blobpool.go:1532 to check the true total count:
if allowance >= 1 && len(p.gappedSource) < maxGapped {This is a one-line, low-risk fix that aligns the enforcement with the documented invariant.
Important
Reviewer guide — stacked PR 2 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 2/6 of the Bor ← go-ethereum v1.17.4 upstream sync: merges
go-ethereum v1.17.0 (upstream range
v1.16.9-tip →v1.17.0stable,0cf3d3ba4) into Bor across 12 review-sized merge batches (~20 upstreamfirst-parent commits each) plus one standalone go-verkle-removal commit and a
milestone-chores commit.
Stacked on #2308 (v1.16.9, milestone 1/6). Base is
ppatil-upstream-v1.16.9for now; it retargets to
upstream-merge-v1.17.4when #2308 merges.merge base advances and upstream history/authorship is preserved. Squash-merging
this PR (or any PR below it in the stack) rewrites SHAs and breaks every stacked
PR above it. Use a merge commit.
What landed
Most of v1.17.0 is bugfix/tooling/test sweeps that adopted cleanly. The
consensus-relevant decisions, per-batch, are in
docs/upstream-merges/v1.17.4/ledger.md; fork decisions infork-register.md;the deferred backlog (with adopt-steps) in
needs-wiring.md.on Bor networks (EIP-8024
ExtraEips-only; Verkle gate nil). No precompile-set,gas-schedule, or activation-block change on any Bor preset.
params.Rules.ChainIDretained (declined upstream removal) to satisfy Bor's precompile-parity guard.
crypto/keccakvendoring (#33323); eip4844latestBlobConfig→(BlobConfig,error)keeping Bor's block-based gating + no-configreturn 0;opKeccak256Keccak256Cache adapted to upstream's hasher; trienode-history(#32621/#33551/#33584) +
NodeFullValueCheckpoint(#32727), both dormant by default;eth_getProofs-for-history; callTracer log index (goldens regenerated preserving Bor's
0x1010fee logs); pathdb history-index mutex combined with upstream's iteratorrefactor; trie decode
CountValueserror check; freezer fd-leak fix.gate + binary-tree +
stateless.Witness).needs-wiring.mdwithadopt-steps — nothing dropped silently): the state code-read metrics line, core/vm
write-protection + selfdestruct cluster, OpenTelemetry line (#33452/#33484/#33521/#33780;
Bor has its own server-side OTel), delayed-p2p-decoding (#33835), EraE format (#32157),
syncModer, slow-block stats, tx-announcement validation, snap-sync locking, legacypool
alloc,
--miner.maxblobs. These clash with Bor divergences (BlockSTM state,SafePool,ExcludeStateSyncReceipt, TD-in-DB, forked downloader, per-fork signer chain).eth/ethconfig/gen_config.go(TrienodeHistory+NodeFullValueCheckpoint+NoSnapServingTOML); exemptedDumpBinTrieLeavesin theStateDB↔ParallelStateDB parity guard.
Executed tests
Beyond the standard CI gates:
go build ./...— clean.go vet ./...— clean except two pre-existing//nolint:govetcopylocks (identical todevelop).go mod tidy— no drift(this milestone adds no dependencies).
go test ./...— green after fixing the only two merge artifacts itsurfaced (
ethclientrpc.NewServersignature;internal/ethapiblockTimestampgoldens). Remaining failures are pre-existing and byte-identical to
develop(VEBLOP miner nil-deref on non-Bor-network test harnesses in
cmd/geth/cmd/devp2p/ethtest;t8n golden drift; a
core/vminterrupt-race flake).make test-integration(-tags integration ./tests/...) — pass, zero failures,including the
tests/borconsensus e2e suite (621s).pre-existing/
develop-inherent dependency/toolchain versions).Not run in-session (operator/CI release gates): diffguard mutation, kurtosis devnet,
and the devnet→testnet soak ladder. Kurtosis marginal value is low here —
tests/bor(consensus e2e) is green and the only observed panics are non-Bor-network paths.
Rollout notes
activation-height change; all adopted features either dormant-by-default or
non-consensus (tooling/RPC/metrics/tests). Deferred upstream features were reverted
to Bor HEAD.
blockTimestampfield oneth_getTransactionBy*RPCresponses (additive); download progress-bar behavior; HTTP/2 JSON-RPC.
timeout (
miner/worker.go:774) so non-Bor-network tests stop panicking; dependencybumps (x/text→v0.39.0, cascade
develop's x/crypto v0.52.0, Go→1.26.5).🤖 Generated with Claude Code