core, params, eth: merge geth v1.17.4 (v1.17.4 sync, milestone 6/6) - #2346
Draft
pratikspatil024 wants to merge 137 commits into
Draft
core, params, eth: merge geth v1.17.4 (v1.17.4 sync, milestone 6/6)#2346pratikspatil024 wants to merge 137 commits into
pratikspatil024 wants to merge 137 commits into
Conversation
…827) Every tracer that implements Stop/GetResult held a `reason error` field that is written by Stop (called from the trace-timeout watchdog goroutine in api.go) and read by GetResult (called by the RPC handler main goroutine). These accesses were unsynchronized.
In the --create path, execFunc returns gasLeft as the second return value, but the rest of the code treats this value as "gas used" (printed as such, and compared in timedExec). This makes gas reporting incorrect and can cause benchmark consistency checks to fail.
This is a refactoring PR to wrap all pre/post-execution system calls as the exported functions, eliminating the duplicated system calls across the codebase. There are a few things unchanged but worths highlight: - ChainMaker is left as unchanged, a significant rewrite is required - BeaconRoot in header should be non-nil if Cancun is enabled --------- Co-authored-by: jwasinger <j-wasinger@hotmail.com>
Fixes the regression caught by https://hive.ethpandaops.io/#/test/generic/1778481210-e59b7465e1d04f7ed1b0200838584b16?testnumber=137. engine.AssembleBlock explicitly expects withdrawals to be non-nil for pre-Shanghai blocks as opposed to FinaliseAndAssemble which stripped off the withdrawal.
In b2843a1, metrics check len(res) == len(hashes) but res is pre-allocated with make(), so length is always equal. Partial hit metric never fires. Count non-nil elements instead. --------- Co-authored-by: Bosul Mun <bsbs8645@snu.ac.kr>
This PR introduces a separate transaction pool type for sparse blobpool. In sparse blobpool, PooledTransactions message delivers transactions without blobs, partial or full cells are downloaded by Cells message. Blobpool no longer stores transactions with complete sidecars, and it stores transactions without blobs, along with the corresponding cells. Because of this, a dedicated type distinct from types.Transaction is required. This PR introduces a type called `BlobTxForPool` and stores each sidecar field independently, in order to bypass the assumption that a sidecar always exists as a complete unit. Reintroducing the conversion queue was considered, but was ultimately omitted because type conversion should be sufficiently fast. With sparse blobpool, blob -> cell computation would take about ~13ms per blob. Not sure whether this is fast enough, but otherwise we can add the conversion queue later on the sparse blobpool branch.
1. should use !reflect.DeepEqual.
2. big.NewInt(0).SetBits([]big.Word{}) work around for DeepEqual when
big.Int is zero, unpack return a []big.Word{}.
Passing `--v2=false` currently still selects the v2 binding generator because the command checks whether the flag was set. This switches generation to use the boolean flag value, so explicit false continues to generate legacy bindings while `--v2` keeps selecting v2.
This PR introduces OnGasChangeV2 tracing hook, as the pre-requisite for landing EIP-8037. --------- Co-authored-by: Sina M <1591639+s1na@users.noreply.github.com>
This PR extends the journal to track the pre-transaction values of mutated balances, nonces, and code. At the end of the transaction, these values are used to filter out no-op changes, such as balance transitions from a-> b->a. These changes are excluded from the block-level access list. Additionally, there is a dedicated `bal.ConstructionBlockAccessList` objects for gathering the state reads and writes within the current transaction. These state writes will be keyed by the block accessList index. --------- Co-authored-by: jwasinger <j-wasinger@hotmail.com>
## Summary
The `--rpc.telemetry.sample-ratio` flag declares `Value: 1.0` and `geth
--help` advertises `(default: 1)`. In practice, however, omitting the
flag produces a sample ratio of `0`, causing
`sdktrace.TraceIDRatioBased(0)` to drop 100% of spans. Users who enable
`--rpc.telemetry` see the `OpenTelemetry trace export enabled` log line
and a clean startup, but no traces ever leave the process.
The root cause is the interaction between two pieces of code:
1. `cmd/utils/flags.go:setOpenTelemetry` (added in #34062) only copies
the flag value when `ctx.IsSet(...)` returns true:
```go
if ctx.IsSet(RPCTelemetrySampleRatioFlag.Name) {
tcfg.SampleRatio = ctx.Float64(RPCTelemetrySampleRatioFlag.Name)
}
```
That is the right pattern for "don't clobber a config-file value with
the CLI default," but it implies that something else must initialise the
field when neither source sets it.
2. `node/defaults.go:DefaultConfig` never initialises
`OpenTelemetry.SampleRatio`, leaving it at the float64 zero value.
The result for the common CLI-only user (no TOML config) is `SampleRatio
= 0` → every span is silently dropped, despite the documented default of
1.
## Change
Seed `OpenTelemetry: OpenTelemetryConfig{SampleRatio: 1.0}` in
`node.DefaultConfig` so the documented default matches runtime behavior
and the `ctx.IsSet` guard in `setOpenTelemetry` continues to do what it
was designed to do.
Avoids every legacy tx hash query hitting the blob pool on the path of BlobPool.GetRLP.
This PR fixes a bug in the current blobpool `Reset` function where it used the Transaction type instead of blobTxForPool. Decoding transactions fetched from the pool as Transaction type caused an error because the blobpool stores blobTxForPool types.
This method is similar to `eth_blobBaseFee` but returns the next base fee.
Return blockchain rewind failures from debug_setHead instead of ignoring them.
…4957) This PR finally lands EIP-7928, collecting the block accessList during the block execution and verifying against the block header. --------- Co-authored-by: jwasinger <j-wasinger@hotmail.com> Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de>
This PR implements the serving side of the eth71 BAL exchange messages. Until commit 4cd7092 also contained the requesting side, but since that part still needs more work, I'm splitting it out into a separate PR. The test injects BALs directly into rawdb. This can be removed once BAL generation is integrated into the chain maker. --------- Co-authored-by: Felix Lange <fjl@twurst.com>
…4967) Updates the static validation logic to cover additional edge cases (reflecting the state of the latest devnet branch, except cleaned up slightly). --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com>
It's a change to BAL json marshalling and t8n tooling to adhere the EELS definition.
…33969) Adds a fast path for ExecutionPayloadEnvelope and BlobAndProofListV* that bypasses encoding/json's reflection and re-validation, which are expensive for large payloads with many blobs. Also hand-rolls the jsonrpcMessage wire encoding in the RPC codec to avoid a second re-validation pass when writing responses to the connection. Resolves #33814 --------- Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de> Co-authored-by: Felix Lange <fjl@twurst.com>
Co-authored-by: healthykim <bsbs8645@snu.ac.kr> Co-authored-by: Felix Lange <fjl@twurst.com>
Fixes a regression where nil results from getBlobs were encoded as an empty array instead of null. --------- Co-authored-by: Felix Lange <fjl@twurst.com>
This PR fixes an issue that when peers legitimately lack a requested BAL, empty (0x80) is delivered and this BAL entry will be refetched over and over again. A `refused` tracker is added and catchUp will fail if this BAL is unavailable against the entire peerset.
`TestTracingHTTPTimeout` still flakes in CI after #35101, failing at the
POST:
--- FAIL: TestTracingHTTPTimeout (0.26s)
tracing_test.go:633: request: Post "http://127.0.0.1:43497": EOF
The test sets a short server `WriteTimeout` and posts a blocking call.
`ContextRequestTimeout` leaves a fixed 100ms for the server to write its
timeout response before the HTTP write deadline cuts the connection.
I can't repro it locally, but my theory is that under load that write
can miss the window, so the connection is dropped and the client POST
returns `EOF`, failing the test before it inspects the span. This is the
only test exposed to it because it is the only one that configures a
`WriteTimeout`.
The EOF is benign: the server sets the timeout error on the SERVER span
before attempting the write, independent of whether the client receives
the response. Since that span status is all the test asserts,
`tryPostJSONRPC` tolerates the transport error instead of failing on it.
The stack primitives pop by value: pop() returns the 32-byte value
itself, so every popped operand is copied out of the stack arena before
it is used. The result side was already in place, peek returns a pointer
and binary ops write into the new stack top. This PR fixes the operand
side: pointer-returning primitives (popPtr, popPtrPeek, etc), with the
handlers rewritten to read operands directly from their arena slots.
Every popped operand paid the copy, whatever the op went on to do with
it, so this optimization covers the arithmetic and comparison ops as
much as JUMP, MSTORE, SSTORE and RETURN.
The copy is visible in the assembly. On arm64, master's opLt spends four
instructions moving the popped value through the frame, and the
comparison then reads it back from there:
LDP (R5), (R6, R7) ; load words 0 and 1 of the popped value from the
arena
LDP 16(R5), (R5, R8) ; load words 2 and 3
STP (R6, R7), vm.~r0-64(SP) ; store words 0 and 1 into a frame slot
STP (R5, R8), vm.~r0-48(SP) ; store words 2 and 3
With popPtrPeek those four instructions are gone, the frame shrinks from
locals=0x58 to locals=0x18, and the function from 336 to 288 bytes. The
compiler cannot remove the copy itself: uint256.Int is a four-element
array, and Go's SSA does not promote arrays longer than one element to
registers, so a by-value pop pays this round trip no matter how far
inlining gets, for LT exactly as for ADD.
The CALL and CREATE families are deliberately not converted: a child
frame reuses the same stack arena, so parent pointers into popped slots
die when the child pushes. The rule is recorded on the primitives:
pointers stay valid until the next push or any sub call. Converting the
call family safely means materializing scalars before the child call,
left for later work with a call-heavy benchmark to justify it.
### Benchmarks
Measured with the benchmark suite from #35144 (the evm-bench contract
workloads and the block import benchmark), which is not part of this
PR's diff. Apple M4 Max, fixed iteration counts, n=10, all p=0.000. B/op
and allocs/op are statistically identical on every benchmark:
| benchmark | master | PR | vs master |
|---|---|---|---|
| Snailtracer | 60.0 ms | 54.1 ms | -9.8% |
| TenThousandHashes | 13.2 ms | 12.2 ms | -7.8% |
| ERC20Transfer | 11.7 ms | 11.0 ms | -5.5% |
| ERC20Mint | 7.49 ms | 7.02 ms | -6.2% |
| ERC20ApprovalTransfer | 8.92 ms | 8.44 ms | -5.4% |
This PR is independent of #35144 but plays nicely with it: the generated
dispatch there splices these handler bodies, so the in-place forms land
in its fast path too, where they measure larger.
### Testing
The rewritten handlers run on the interpreter's only execution path, so
correctness rests on references outside the change:
- **Consensus fixtures.** The full tests package passes: state tests,
the execution-spec families, blockchain tests.
- **Opcode testcases.** The JSON testcases compare individual opcode
results against committed expected values.
- **Tracer fixtures.** The tracetest reference files pin exact log and
return data shapes, covering the rewritten LOG and RETURN paths.
- **Cross-build differential.** A goevmlab campaign running this
branch's evm against master's evm over generated state tests across four
forks (Prague, Cancun, London, Osaka) with full trace comparison:
160,566 tests, zero divergences.
---------
Co-authored-by: MariusVanDerWijden <m.vanderwijden@live.de>
… (#35170) sendInvalidTxs's *eth.TransactionsPacket case iterated `txs` — the locally-sent invalid transactions, every one of which is in `invalids` by construction — instead of the transactions actually carried by the received packet. As a result the loop returned "received bad tx" on the very first TransactionsPacket the peer sent, regardless of its contents, and never inspected what was really propagated. Iterate msg.Items() (the decoded contents of the received packet) so the "node must not propagate invalid txs" conformance check tests the real condition instead of producing a false negative. --------- Co-authored-by: Bosul Mun <bsbs8645@snu.ac.kr>
This PR improves the slot reservation logic in the context of snap/2. Geth has the mechanism to reserve roughly half the peer slots for peers supporting the snap protocol if snap syncing is needed by local node. With the context of snap/2, this mechanism should be changed that: we reserve the slot for the "usable snap peer", not blindly for peer with snap extension enabled (such as legacy snap/1, which can't serve the snap/2).
…181) This PR introduces a new condition that if the local node falls behind too much and the required BAL for catching up is very likely to be unavailable, the entire snap sync will be restarting from scratch. As the defined BAL retention window is weak-subjective-period which is calculated dynamically. A more conservative threshold is used (90K blocks) for robustness. Apart from that, the BAL catchup will be divided into several spans and apply one by one. It's essential to prevent the potential out-of-memory panic of placing the entire BAL set in memory.
This PR does two things: - Expose snap/2 specific sync progress fields - Seed the sync progress after `loadSyncStatus `
This PR fixes an issue where flat states are continuously persisted during downloadState, while the sync journal is only persisted at the end of Sync. As a result, an unclean shutdown can leave the on-disk flat state ahead of the journal markers. Some persisted entries may be stale (storage slots that should have been deleted), and these dangling entries are not detected or fixed by subsequent state downloads. To address this, this PR introduces a cleanup step before state downloading begins. It removes all state entries that are not covered by the persisted journal markers.
Adds `testing_commitBlockV1`. It is the write companion of `testing_buildBlockV1`: it builds a block from the provided payload attributes and transactions on top of the current canonical head, inserts it, and sets it as the new head, returning the new head hash. --------- Co-authored-by: MariusVanDerWijden <m.vanderwijden@live.de>
Since go 1.18 reflect has `reflect.Pointer` which replaces `reflect.Ptr`. Newer versions of `govet` will alert. See also: https://pkg.go.dev/reflect#pkg-constants
The timer should wait the remaining time, not the elapsed time.
20 upstream first-parent commits, 27 conflicted files across 46 hunks. The batch is dominated by two refactors of shared execution plumbing, and both were adopted because declining them had become the expensive option rather than the safe one. Witness generation, propagation and import are untouched. The stateless package, the witness protocol and the downloader have no diff at all. Three files on the guarded list did change, and every changed hunk in them was scanned for witness symbols rather than trusted by filename, with no hits. No fork gate moved and no new fork or EIP arrived. The chain config, the runtime presets, the packaged genesis files, the jump table and the fork id calculation are all unchanged, so neither fork meta-guard needed a new entry. The only hardfork-adjacent change is upstream's move to a two-dimensional gas-change tracing hook, which alters how a gas event is dispatched and not what any opcode or precompile costs. Amsterdam, Verkle and the binary trie stay dormant. The most consequential resolution is in the state processor's prologue, and it would have compiled either way. Upstream's variable block auto-merged over bor's and builds the block context with a nil author, where bor passes the author it resolved. That value becomes the context's coinbase, and bor derives the block author from the header signature rather than from the header's coinbase field, so a silent nil there changes fee distribution and the coinbase opcode. Bor's own side of the same conflict could not have compiled either, because it re-declared the EVM that the merged variable block now creates. The resolution restores the author in the merged block and drops the duplicated construction. This is the same hazard as the deleted slice in an earlier batch, one step more dangerous, because there the build failed and here it would not have. The first refactor renames and re-types upstream's own block-level access list field and replaces the journal's dirty-account counter with a richer per-account mutation map. The old field name reads like bor's parallel-execution read set and is not; it is upstream's, present at the merge base. By the time the conflicts surfaced, the struct field, the getter, the constructor and the finalisation aggregation had all auto-merged, leaving only the two references inside the markers, so adopting cost one line per site while declining would have meant reverting a twelve-file footprint. Bor's divergences around those lines are kept: the multi-version read wrapper and the timer skips in the account getter, and the storage mutex in the committed-state reader. The journal change stranded six bor-only call sites with no conflict. Two iterate the dirty address set, which the new map keys identically, and four are the direct bulk setters used during parallel settlement, where the old call did nothing but ensure key membership and the new one has the same effect. A bare entry is safe there because every access list aggregation is guarded by its own per-field flag, so finalisation is restored without recording access-list data, which is the behaviour parallel execution already documents. The same refactor also promotes the transaction-context setter into the state interface, so the parallel state database now implements it, carrying only the index: it is handed its index at construction, attributes logs at settlement, and builds no access list, so the hash and access index have nothing to bind to. That implementation made an existing exemption stale. The second refactor wraps the pre- and post-execution system calls into exported helpers. It does not touch block finalisation or assembly, so the assembly decision recorded earlier in this sync is undisturbed; the assembly rework lands in the next batch. The helpers are adopted with their bodies rewritten for bor: the tracing spans are removed because bor has no telemetry package at all, the timestamp-based fork predicates become bor's block-based pair, and the arena release is dropped as it has been throughout. The context argument is kept in both signatures so the eleven call sites that already reference these helpers compile unchanged and future merges stay aligned, even though it is now unused. Where bor's semantics differ, the gate is preserved rather than the code: block processing keeps its inlined request block, since the helper cannot express the condition that suppresses those requests on bor chains and the block is interleaved with state-sync tracing and bor's own finalisation signature. Three lower-risk callers do route through the new helper, wrapped in that same condition, which is equivalent because the fork test now lives inside the helper. Elsewhere: the snap-sync head-hash write is re-seated where bor relocated the head assignment, since upstream's new line targeted a site bor had emptied and would otherwise have been lost; the blob pool combines bor's block-based fork predicate with upstream's new pooled-transaction accessors, which were mandatory because the code below them had already auto-merged; the virtual-host handler adopts request-side case normalisation, the missing half of a pair whose map was already lowercased at construction; and two upstream bug fixes are taken whose bugs bor shared, an unconditional scheme overwrite and an inverted test assertion. The base-fee accessor is an add-and-add where bor had already shipped the same method, and bor's version is kept because its gate is the correct one: the calculation returns the base fee of the block after its argument, so the fork test belongs one block ahead. The matching public method and a mock converge onto upstream instead, retiring two permanent divergences at no cost. The simulated-block withdrawals case is a genuine combine where neither side alone is correct. Upstream's new fix only attaches withdrawals once the relevant fork is active but still dereferences the override unconditionally inside that branch, relying on sanitisation that does not hold on bor, where the override stays nil. Keeping bor's side would have dropped the fix. The resolution keeps both the fork gate and the nil check. Nine sites broke at build or vet with no conflict to announce them, including a second landing site for the declined telemetry defaults that auto-merged silently. Six of those are test files reachable only with a build tag, which is why this batch adds a tag-enabled vet sweep to the per-batch gates: the previous milestone shipped an unused import in exactly such a file, invisible to the ordinary build, the untagged vet and the neighbouring integration package. Two more bor-only drift guards fired and were updated, one requiring every state method to exist on the parallel implementation or be exempted, the other requiring every tracing hook to be classified. Two tests new in this batch assume upstream's configuration shape and were adapted: one omits the post-merge marker that bor's chain maker needs, and one reaches for the mainnet config where its seven siblings use a purpose-built one with block-based fork keys, which on bor activates neither blob transactions nor the blob schedule. Six test failures are pre-existing and were confirmed by re-running at the merge base and comparing the failure text rather than the test names. Two are timing races between a five-millisecond abort and a ten-million-gas loop, which pass in isolation at both revisions and fail only under parallel load at both. Four are golden-output mismatches in the evm command, including a state root mismatch that resolves to an unchanged root: the base computes the same value against the same stale expectation.
20 upstream first-parent commits, 26 conflicted files. This is the batch the plan flagged from the start, and it reached both of the points where this sync stops and asks rather than resolving. Two clusters were declined with explicit approval, and both are documented for the day they have to land here. Witness generation, propagation and import are untouched, and this time that was the decision rather than an observation. Upstream reworked witness serialization so the external form reverses header order and sorts codes and state nodes, to match the execution-spec ordering for a new engine method that carries a witness. That is reachable here: bor's own wire format is a three-field encoding of its own, but the decoder falls back to the external form when a peer sends the older five-field layout, and the same conversion backs the debug witness endpoints. Adopting only the upstream side would leave the encoder writing one order and the decoder assuming another on that fallback path, which is an interop break rather than a formatting change. The motivation does not reach bor either, since the engine API is inherited but unused and the file implementing the witness variant of it was already deleted here. Declined, with bor's context field preserved on both conversions. Upstream's new round-trip test for the external form arrived as an add-and-add conflict and bor's existing tests were kept. The second decline is the block access list construction cluster, five commits that build the list through execution, serve it at a new protocol version, rework its JSON, add static validation, and wire it into the transition tool. The construction commit changes the consensus engine interface itself, adding an access index and a list to Finalize. That is where bor diverges hardest: bor's Finalize takes and returns receipts because it applies state sync events and appends the state sync receipt, and bor keeps an assemble variant upstream no longer has. Upstream also builds the list unconditionally per block while gating it inside the pre and post execution helpers, so on bor it would allocate for every block and merge nothing, in exchange for changing the most consensus-critical signature in the tree. The feature is dormant here, and it cannot run under bor's parallel executor at all, which is already recorded. Declined as one unit, since the later commits build on the first. The resulting position is deliberate and stated plainly in the register: after the previous batch and this one, bor records state accesses into a per-transaction access list but never creates one. The recording sites are all behind a nil guard, and the list is allocated only once the dormant fork is active. Nothing assembles, merges, validates or serves a block-level list, so the header field stays empty and the verifier keeps asserting exactly that. Scheduling the fork now requires the construction cluster and the parallel-executor decision together, because neither alone produces a valid list. Declining a cluster means reverting its whole footprint rather than its conflicts. The union here is forty-seven paths against fourteen conflicts, so a conflict-only rollback would have left roughly thirty files carrying declined code, almost all of it merged silently. The footprint was computed from upstream history, every path restored to this fork's tip or removed, and the kept commits touching those same files re-applied in order afterwards: the engine payload types, the chain maker's slot number on bor's block-based gate, and the rewind error propagation. Three residues survived that sweep, each caught by a different gate. The payload data codec was renamed by a later kept commit, so the declined access-list plumbing outlived the rollback under a filename the footprint could not match, and was stripped by hand; that is a general hazard when declining a commit whose files a later kept commit renames. The engine API's blob endpoints gained tracing spans that merged in while this fork's import block won, leaving undefined references; reverting the file would have dropped three kept commits, so the spans were removed and the context parameters kept, and a counter that existed only to fill a span attribute went with it while its counterpart that drives the partial-response decision stayed. Four cache meters merged in whose only consumer is a file this fork deleted long ago, which the build accepts and the linter does not, so they were reverted too. The largest adoption was forced rather than chosen. The JSON-RPC message's error field merged from a struct pointer to pre-marshalled bytes, so every reader of its code and message had to move to the new accessor and no side of those conflicts would have compiled alone. The codec split into separate message and batch encoders, with hand-written builders and a helper that preserves the existing content-length, identity-transfer-encoding and flush behaviour for error responses exactly, only relocated. This fork's divergences there were blank lines and one explicit discard on the batch write, which was kept. The rework requires a new direct dependency for JSON writing, which is worth a reviewer's attention since the supply-chain check will report it. Elsewhere: the rewind endpoint now returns its error instead of discarding it; the generated payload envelope encoder was replaced by the hand-written one, which would otherwise have been defined twice; a new seven-hundred-line engine benchmark was adapted rather than dropped, moving two timestamp fork fields to their block-based equivalents and removing context arguments from the engine calls that do not take one here; and three modify-and-delete conflicts on files this fork had already deleted were all kept deleted, each matching a decision already on record. No fork gate moved and no new fork or EIP arrived. The chain config, the runtime presets, the packaged genesis files, the jump table and the fork id are untouched, and the protocol parameters file was inside the declined footprint and is back at this fork's tip. Neither meta-guard needed an entry.
20 upstream first-parent commits, 25 conflicted files. After the two declines of the previous batch this one is comparatively ordinary, and it is the first of the milestone where both standing scans come back completely empty: no file under the stateless package, the witness protocol, the state database, the blockchain, the miner or the downloader is touched at all, and neither the chain config, the runtime presets, the packaged genesis files, the precompile table, the jump table nor the fork id calculation changed. No fork gate moved, no new fork or EIP arrived, and neither meta-guard needed an entry. One item needed a decision rather than a resolution, and for an unusual reason: this fork already has the optimisation upstream is adding, at a different scope. Upstream adds a process-global sharded cache of jump-destination bitmaps keyed by code hash, with hit and miss meters, threaded as a new parameter through the prefetcher and the processor. This fork already shares such a cache between the prefetcher goroutine and the parallel execution workers, but per block: a fresh map is created for each block and discarded afterwards. So the two are not duplicates, they differ in lifetime. Upstream's would skip the analysis entirely after a contract is first seen rather than once per block, and it is bounded where this fork's is not, which is plausibly the larger win on a workload that concentrates on a small set of contracts. Declined for this batch on scope grounds, with approval. Adopting the parameter threading collides with this fork's diverged prefetcher signatures, and the variant actually worth having — pointing the existing shared-cache config field at a process-global bounded cache, which needs no signature changes at all — is a design change to this fork rather than a merge resolution, and it belongs on its own branch with before-and-after measurements on the execution hot path. It is recorded as a benchmarked follow-up, including the two invariants to settle first and the instruments to measure with. The new file was removed along with the decline, since with nothing referencing it its unexported meters and constants are dead code the linter reports, the same shape as the orphaned cache meters in the previous batch. A decision from two batches ago was vindicated by upstream. That batch hit an add-and-add on the backend's base fee accessor and kept this fork's version, arguing that because the calculation returns the base fee of the block *after* its argument, gating on the next block number was correct and upstream's gate on the current one was subtly wrong at the fork boundary. Upstream has now fixed exactly that, arriving at byte-identical logic, so the conflict resolved by converging onto upstream and the divergence is retired at no cost. This fork was also already ahead on the STUN dependency bump, carrying version three before the merge, so that conflict resolved to this side and the module files kept their state. The deprecated flag removal was accepted after two checks rather than on faith. None of the fourteen removed flags is referenced outside the files upstream edits, and the binary this fork ships is built from a different entry point with its flags declared elsewhere, so the removal cannot change what operators pass to it. One hazard inside the cluster: upstream's side of the miner-config hunk keeps a blob-cap flag this fork declined long ago, so that hunk had to collapse to neither side instead of taking upstream's. The removal of the USB parameter from the account-manager and signer constructors was taken consistently across the clef command and the signer package. Two residues surfaced from gates rather than from reading the diff. The legacy flag file kept two imports whose only users upstream deleted, which the build caught, the same class as the unused import behind a build tag that broke CI at the end of the previous milestone. And the request handler ended up checking the method-name length twice, because upstream moved the check to the top of the function while this fork's copy survived further down; the inner one was removed in favour of upstream's single early check. Elsewhere: the engine API now imports a payload at genesis even when snap sync is configured, combining upstream's new exception with this fork's own sync-mode predicate; the HTTP handler stack gains the gzip opt-out next to this fork's execution-pool field; two test mocks were combined rather than replaced; and two modify-and-delete conflicts on files this fork does not carry, an upstream CI workflow and the handler file from the declined snap protocol split, were both kept deleted. Verification covered every touched package with no failures. The command package for the inherited geth binary fails seven tests on a panic in this fork's period calculation reached from the miner work loop, which was reproduced at the previous batch with an identical panic frame and is therefore pre-existing; that also clears the flag removal of suspicion. Worth recording why CI is green on it: this fork's test target excludes the whole command tree, so those tests have never been part of the gate. The panic itself is a real latent bug in an inherited but unused surface, where starting on a non-bor genesis reaches a nil consensus config, and is worth filing separately.
20 upstream first-parent commits, 38 conflicted files. That is the largest conflict count of the milestone, but it is misleading: the conflicts collapse into only nine upstream commits, and the batch is really one structural adoption plus two extensions of deferrals that already existed. Both standing scans come back empty for the second batch running. No file under the stateless package, the witness protocol, the state database, the miner or the downloader changed; the blockchain has exactly one changed line, a freezer tail lookup that now names a tail group. Neither the chain config, the runtime presets, the packaged genesis files, the precompile table, the jump table nor the fork id calculation moved, no new fork or EIP arrived, and neither meta-guard needed an entry. The miner work loop is worth stating explicitly, since it conflicted and it is the file on the no-go list that matters most. The conflict was telemetry attribute types only, an integer helper replacing a widened one, from a tracing commit this fork cannot carry. The file is now byte-identical to its previous state. A file overlapping is not the feature overlapping. The structural item is upstream's flat-file storage for finalized block access lists. Despite the title it is not an access-list feature: it is a general rework of the freezer, replacing a per-table prunable boolean with a named tail group so that different groups within one freezer can keep independent tails, adding the ability to attach a new table to an existing freezer, and changing the ancient-store interface so that reading and truncating a tail both take a group name. The access-list table rides on top of that. The general layer was not really a choice. The interface change had already merged cleanly, and with it the table wrapper, the in-memory freezer, the ancient utilities, the shared test suite and all six callers in the path-based trie database: thirteen of the twenty-four files in the footprint. Neither side compiled alone, the same shape as the forced adoption two batches ago. Declining would have meant reverting an interface change across thirteen cleanly merged files and then paying for it on every future freezer commit. The real decision was the access-list table itself, and it was taken in full with approval. Nothing in this fork writes such a list, so the table holds a nil placeholder per frozen block until the construction cluster deferred two batches ago is adopted. The upgrade path was verified rather than assumed: the repair routine takes the common head as the minimum over non-empty tables and fast-forwards a freshly added empty table to it, so an existing database gains the new table at its current head with no backfill and no resync. Upstream ships a test for exactly that, and it passes here. The ongoing cost is index only, six bytes per frozen block for a table that stays empty. Taking the general layer while stripping the table would have bought that back in exchange for a permanent divergence in three storage-layer functions that would conflict on every future freezer change and would have to be undone anyway once the fork is scheduled. Two adaptations were needed for this fork. It carries two extra chain tables, total difficulty and the state-sync receipt, both previously prunable; under the group model every prunable table must name a group, and since both are written per block alongside bodies and receipts they join the block-data group. That reproduces the previous semantics exactly, one shared tail across all prunable chain tables, while the new table gets its own group so it can be pruned independently. Separately, this fork's freezer carries an offset for pruned-ancient stores, and the repair and validate routines were rewritten wholesale upstream and merged cleanly, so they were audited line by line: all seventeen uses of the offset survive, including the one inside a conflict, and the offset test passes. Two deferrals were extended, both with approval. The archive-format rewrite deferred long ago received a spec update; almost all of it lands in a package this fork does not carry, which arrived as modify-and-delete conflicts and was removed again, while the files it does carry keep their existing form because every change in them presupposes the deferred interface layer and a flag this fork lacks. And the deferred snap protocol split gained a skeleton: a three-thousand-line parallel copy of the syncer, wired to nothing. Removed, on the same reasoning as the global cache file in the previous batch, since unreferenced code is dead weight the linter reports. The batch two ahead carries the sync logic that would consume it, so the decision will be revisited there as one unit. The tracing work was declined as usual, but one real fix was hiding inside it. The HTTP transport merged cleanly and pulled in an import of a package this fork does not have, which would not have compiled; the same region also carries an independent fix that always sets the content length on responses, whose stated precondition this fork gained two batches ago. So that file was combined rather than reverted: the fix and the context threading are kept, only the span and its import are stripped, and the error path keeps the identity transfer encoding and the explicit flush that were hand-preserved earlier. The service registry produced another vindication: its conflict shows the merge base with a three-value lookup introduced by a tracing commit this fork declined, while both sides now return a single value. Upstream has converged back onto this fork's shape. Upstream also moved three chain-head subscriptions from run loops into constructors. In all three the constructor half merged cleanly while the loop half conflicted, so keeping the previous form was not an option; it would have subscribed twice in the transaction pool. This fork has a deliberate divergence here, a defensive nil check around unsubscribe added by its own commit in exactly these places, and that check is preserved, including on the one deferred statement upstream wrote without it. Two interface implementers outside upstream's footprint were found by the twin scan before resolving rather than by a gate. One is a generated mock that exists only here; it was regenerated with the command recorded in its own header rather than hand-edited, and exactly eight lines changed. The other is a test double carrying a compile-time interface assertion, which would have broken the test build. Two upstream fixtures were adapted. One called a write helper with upstream's argument count where this fork's takes two more. The other, the new-table alignment test, needed the offset argument and, less obviously, had to populate this fork's two extra tables: they share the block-data group, the repair routine takes the maximum tail within a group, and leaving them empty would fast-forward the whole group past the very data the test reads back. A comment records why. That property is safe in production because every block-data table is written together for each frozen block, and on a fresh database all of them are empty so the head stays at zero. Verification covered every touched package with no failures, including the integration suite. The state-test fixtures are not present in this working copy so those tests skip locally, which is why the vet pass with the integration tag matters: it is what proves the tagged files still compile.
20 upstream first-parent commits, 34 conflicted files. They collapse into ten upstream commits, and nineteen of the thirty-four belong to a single one: upstream has removed its stand-alone signing tool. That makes this the only batch of the sync that is overwhelmingly a deletion, a little over ten thousand lines of it. Both standing scans come back clean for the third batch running. No file under the stateless package, the witness protocol, the state database, the blockchain or the downloader changed; the only change anywhere under the miner is one line of a test failure message, an off-by-one in an index from the sweep that fixed incorrect variables in error strings. Neither the chain config, the runtime presets, the packaged genesis files, the precompile table, the jump table nor the fork id calculation moved, no new fork or EIP arrived, and neither meta-guard needed an entry. The tool removal was accepted, but on evidence rather than on the fact that upstream did it. Three things were checked. This fork never builds the binary: its makefile, its CI build script, its release configuration and its docker files contain no reference to it at all, so no release artifact has ever contained it. Nothing outside the tool itself imports the packages being deleted: a first search looked alarming, because the external account backend and an ABI dump command both appeared to match, but that was a prefix match on a subpackage of shared type definitions which upstream keeps, along with the four-byte database. And the capability people actually care about is untouched, because the external signer backend is an RPC client that talks to any signer implementing the protocol; the deleted binary was one implementation of it, not the mechanism. Worth noting that this fork had modified two of the deleted files only one batch earlier, in the deprecated-flag removal, so that work is discarded here. The dependency tidy corroborated the removal independently: three table-formatting and terminal-width libraries dropped out of the secondary module's graph, which were the deleted tool's console dependencies. Two documentation files also conflicted, but both are complete rewrites on this side, so both keep their existing content; a search for the tool's name appears to hit in them at first, but only because the conflict regions in the working tree still contain upstream's text. The batch's real find was a bor-only interface broken by a clean merge. Upstream changed a balance-reading API method to take its block parameter by pointer, so that it can be omitted and defaulted to the latest block, and that file merged without conflict. This fork, however, declares its own interface that the consensus engine uses to reach those methods, and it still named the value form; the concrete type stopped satisfying it and the config package failed to compile at four construction sites. The build caught it. The fix carries no behavioural risk: that method is never actually invoked anywhere in the tree, existing only so the concrete API type remains assignable, and the interface's two other methods already took the pointer form, so the change is also the locally consistent one. The generated mock beside it was regenerated with the command recorded in its own header rather than hand-edited. The snapshot generation shutdown race fix was adopted wholesale. It replaces a channel-of-channels abort handshake with a cancel and done pair plus an explicit stop method, and deletes a wrapper error type. Adopting was straightforward because this fork's divergence in every one of those eight conflicts is blank-line placement, plus a single linter directive attached to the type upstream deletes. It was confirmed first that nothing outside the package touches the old mechanism; the only other match in the tree is an unrelated local variable of this fork's own parallel-execution abort type. One conflict was a genuine combine on a fork-gated path. Upstream now drops a reorged-out legacy blob transaction after the relevant fork instead of upgrading its sidecar, and the merge had already pulled in the new comment saying exactly that, directly above the old code that still performed the upgrade — so the function contradicted itself. The resolution takes upstream's drop-and-error behaviour with this fork's block-based fork predicate rather than upstream's timestamp-based one, which is the standing rule for every fork gate in this sync. For once a tracing commit could be taken. Every one so far has been declined because this fork has no equivalent of upstream's telemetry wrapper package, but the propagator option added to the RPC client depends only on the upstream OpenTelemetry propagation package, and this fork does carry OpenTelemetry along with its own tracing helpers. The distinction is worth stating plainly: what is missing is the wrapper, not the library. The accompanying test file was still kept deleted. A validation tightening on block access list encoding was kept out, extending an earlier decline: this fork's copy of that file is an older shape, without the per-slot validate method the fix edits, because the JSON encoding and static validation commits were both declined three batches ago as part of one cluster. Elsewhere: the console binding for a new transaction-pool clearing method was added at upstream's exact position, since its backend merged cleanly; a websocket request on a non-matching path now returns a not-found status instead of closing silently; an array-parsing error that reported the wrong character was fixed; and a test helper this fork had generalised keeps its generic signature while taking upstream's spelling correction. Three upstream test fixtures needed this fork's signatures, all caught by the vet pass rather than the build: a new API test was combined with the two existing local tests instead of replacing them and had its server constructor given this fork's execution-pool arguments; a new pool test was given the extra interrupt argument this fork's pending-transaction method takes, passing nil, which the legacy subpool handles explicitly; and a duplicated import was removed where this fork groups it differently. Verification covered every touched package with no failures, including the integration suite. One inherited command package panics on a nil consensus config reached from the miner work loop. That is the same latent bug found two batches ago behind seven failures in the other inherited command, and it was re-checked rather than assumed: the package was run at the previous batch's commit in a throwaway worktree and panics with an identical frame. Neither the miner nor the params package is touched here. The finding widens what is known about that bug — it affects any inherited command that starts a node with a miner on a non-bor genesis — and since this fork's test target excludes the whole command tree, CI has never exercised either case. Still worth filing separately.
20 upstream first-parent commits, 39 conflicted files collapsing into ten commits. The batch has two halves that pull in opposite directions: the state-creation gas EIP had to be taken whole because two thirds of its footprint arrived already merged, while the BAL-based state sync protocol was declined for the fourth time. Both standing scans come back clean for the fourth batch running. Nothing under the stateless package or the witness protocol appears anywhere in the range, and no upstream hunk in it mentions witness at all. The hardfork surface does move this time, and the reasoning is recorded in full in the fork register: no new fork, no activation height, no precompile added, removed or re-gated, and the behaviour is gated on a fork that is nil on every network preset. This fork's gate for it was already the block-based form, so no conversion was needed. The gas EIP was adopted because declining had stopped being an option: twenty-five of its thirty-six files auto-merged, including the whole gas-accounting substrate — the block gas pool, the budget type, the interpreter, the jump table and the test harness — so refusing it would have meant reverting all of them and re-deriving a model the conflicted files already depend on. That makes this the fourth forced adoption of the sync and by far the largest. The substantive change is a gas reservoir: every frame now snapshots its state gas on entry and returns an exit-form budget rather than the running one. Adopting that on a consensus path needed an equivalence argument, and it is exact — with state gas at zero, which is this fork's situation everywhere because state gas is only ever charged in the dormant branch, the halt form consumes precisely the remaining regular gas the old code consumed, the revert form preserves it exactly as the old guard did, and the success form is the identity. Two divergences needed care rather than resolution. This fork raises the deployed-code size cap on its own fork and, unlike upstream, assigns the error instead of returning so the deployment gas is still charged; upstream meanwhile deleted the single size check and pushed it into each of three gas-charging branches, deliberately before the charge in the new fork's branch. Both properties are preserved by routing all three of upstream's new call sites through a small helper that applies this fork's cap first. Upstream's early return is gas-equivalent to the assign-and-continue flow on every path reachable here, because the deployment gas is charged before the size check returns; the only difference is that the code is no longer written before the error surfaces, which is unobservable since the caller reverts the snapshot either way. The second is a deletion that looks alarming and is not. The same commit removes the static-call write-protection guard from both contract-creation opcodes, and the creation helper does not check it, which read alone would permit contract creation inside a static call. It does not, because the commit moves the check into the four creation gas functions — six added lines against two removed — and the interpreter evaluates dynamic gas before the execution function. All four are wired in this fork's jump tables and overrides, and this fork wraps a dynamic-gas error exactly as upstream does, so the observable behaviour matches. Keeping the old guard would have been unreachable code that also diverged from upstream's error text. The transaction-level half needed rewiring instead. Upstream extracted a settlement method, which merged cleanly, and deleted both the frozen initial budget and the gas-used accessor, leaving six callers of a method that no longer exists. All six were repointed at the locals the new method returns while keeping every local divergence in that function: the parallel-execution fee flags, the burnt-contract redirect with its nil-config guard, the balance snapshots that feed the execution result, the signed big-integer tip, and this fork's arm of the transaction gas cap. Both fork gates in the file stay block-based; a second timestamp-form call site had auto-merged and was corrected. The sync protocol decline was approved as one unit and rests on a structural argument that did not exist at the two earlier deferrals. This fork renamed the downloader and diverged it to twice upstream's size, so all four commits here that touch upstream's downloader have no landing site at all — adoption is no longer porting a handler split but re-porting a two-and-a-half-thousand-line downloader onto upstream's new state machine, plus replacing a commit-head method its own interface requires. Meanwhile the feature remains unusable, because it exists to serve block access lists and this fork can neither produce one nor build them under parallel execution. Nine files were removed and twenty-six reverted; every removal was checked against the previous commit first so none could be a local file lost by accident, and the auto-merged config field and command-line flag were stripped by hand. Three salvage candidates were examined and all three rejected on evidence — one is plumbing feeding only the declined progress counter, one would have added three permanently-zero fields to user-facing sync output, and the third turns out to be an orphan rather than a fix this fork is missing, because its commit-head path already regenerates the snapshot and resumes paused maintenance. The blob-cache commit was declined as one unit with a package move, because the move exists only to let the cache share the miner's selection logic. The cache cannot run here: the blob pool field is declared and never assigned, since the pool was removed from the subpool list outright, and the engine API that consumes it is inherited but unused, so adopting would have wrapped a nil pool. The move would also have been expensive for nothing — this fork's ordering file is a third larger, its constructor takes an extra interrupt parameter, it adds four methods, and about twenty call sites in the block-building path use the unexported local type. The stack refactor was the second forced adoption and the one that lands in the hottest code in the tree. Upstream added seven methods returning pointers into the stack instead of copying values, and this fork's stack is a different implementation entirely — a fixed array with a leading counter, ported from another interpreter, against upstream's shared-backing design. Declining was unavailable because twenty-one call sites in one file and two in another auto-merged outside any conflict region, so the package could not compile without them. The seven were translated, which is mechanical and semantically identical, and upstream kept the value-copy pop so the remaining thirteen local callers still compile. All twenty-four conflicts in the instruction file plus one elsewhere turned out to be pure blank-line divergences — not assumed, but established by a resolver that compares against the merge base with blank lines stripped and refuses any hunk whose substance differs. It flagged none there and flagged exactly the eleven that genuinely needed hand resolution. The container memory-limit fix was adopted, and it carries a finding worth acting on separately: the library it replaces is still used in this fork's own server configuration, in a near-verbatim copy of the very block upstream fixed — same clamp, same allowance, same warning — and that is the path a normal configured startup takes. The fix therefore landed only on the inherited command-line path. Porting it changes cache sizing on the production startup path, so it is recorded as a follow-up rather than smuggled in here. A conformance test fix was ported rather than taken or dropped: upstream's version depends on a lazy decoding wrapper this fork does not carry, but the false negative it fixes exists here too, so it was expressed against the local packet shape. The block-building test API change is the fourth orphaned change on a feature declined twenty batches ago. Six local implementers of upstream interfaces broke outside upstream's footprint, five of them surfaced by the build. An ecrecover cache fast path whose own comment says it mirrors the precompile runner got exactly the edit upstream made to the function it mirrors; the state-sync system-call processor, in a directory upstream does not have at all, took the new zero state budget; the tracer moved to the new intrinsic-gas signature; a prefetch loop followed a pure rename; nine local virtual-machine tests took the new argument; and a generated helper called an accessor this fork had removed entirely, so the predicate was written in the local idiom instead of reintroducing it — which is also the block-based form the standing rule requires. Verification covers every touched package, plus the integration-tagged vet pass, both module tidies and the linter, which reports no issues and so also confirms the declines left no orphaned symbols. Two vet findings and two failing inherited command packages are pre-existing, all re-baselined at the previous commit rather than assumed, and all reproducing identically — the two packages carry the still-unfiled nil consensus-config panic reached from the miner work loop, which this fork's test target excludes from CI entirely. Two local tests failed on the first run and both were genuine regressions from this batch, each re-baselined and fixed. One was a timing race the stack optimisation outran: gas parity is exact on both paths, but the fast dispatch now burns the test's budget in under six milliseconds against a five-millisecond sleep, so the budget was raised in the two tests that race a wall clock. The other was a fork-parity guard doing its job — the gas EIP adds the first state-processor-level branch on the new fork, and before amending the expectation it was verified that both execution paths reach it through the same four shared system-call helpers, so no divergence exists; the asymmetry is where the helper is defined.
The final batch, and the smallest of the sync: 4 upstream first-parent commits, 15 files offered, 14 taken, 3 conflicts over two commits. The boundary is the release tag itself, so this lands the target the sync has been working toward. Both standing scans come back clean for the fifth batch running, and this time trivially — no file under the stateless package or the witness protocol appears in the range, and the range contains no hunk mentioning witness in any file at all. Not one entry on the hardfork surface list is touched either: no chain config, no runtime preset, no packaged genesis, no precompile table, no jump table, no fork id. Nothing moves that either meta-guard tracks. Two of the three conflicts belong to a tree-wide sweep replacing a deprecated reflection constant with its current name. Ten of its twelve files merged cleanly; the two that did not diverge from the merge base by a blank line each, which was established rather than assumed by the same resolver used in the previous batch — it refuses and flags any hunk whose substance differs, and it flagged neither. One local file the sweep could not reach, in a command directory upstream does not have, was completed by hand. That one compiles either way, because the old name is a true alias in the standard library and even carries an automated-fix directive, but an all-tree sweep that leaves a single file on the deprecated spelling is one the next linter run re-opens. The third conflict is the version file, and it was kept local again. The batch ends at upstream's release tag, which sets a patch number and marks the build stable. This follows the precedent set two milestones ago rather than the one from the middle of the sync, and the distinction is real: the earlier take-theirs was a release-*cycle* commit opening a new line, whereas a release *tag* has been declined every time one has appeared. The evidence behind that precedent was re-gathered rather than trusted — every milestone tip of this sync still reads the local values, and the two branches that do not are both explained, one being the first batch's release-cycle auto-merge and the other the base branch cut before the sync started. This also retires a stale plan item: the bump had been carried as something for the chores commit to settle, and it is not — it is simply not taken. What does become actionable is the question that decision parked, namely what this file should read for a tree whose base really is this release; until now that was hypothetical. It stays untouched here because it is a release decision rather than a merge one. One commit produced no change at all, and that was verified rather than assumed, because a footprint reporting "unchanged" is otherwise indistinguishable from a change that silently failed to apply. It fixes a discovery iterator waiting the elapsed interval instead of the remaining one, and this tree already had the corrected expression, introduced locally by an earlier lint cleanup — so all three sides agreed and the merge was genuinely a no-op. That makes the fifth time in this sync that upstream has converged onto something already present here. The remaining commit, a log message in the path-based trie's obsolete-index cleanup, merged without incident. Verification is the full per-batch tier: build, both vet passes including the integration-tagged one, formatting over every touched file, both module tidies, and the linter with no issues. Every touched package passes, along with the bor test suite. No regressions and no new failures; the only vet output is the two pre-existing lock-copy findings in files this batch does not touch, already re-baselined in earlier batches. With this the merge work for the sync is complete: the branch now carries upstream history through the release tag, in 33 batches, each a single merge commit with its reasoning recorded.
Documentation only; no code changes. Covers batches 27 through 33, closing the final milestone. With these the merge work for the whole sync is complete: the branch carries upstream history through the release tag, in 33 batches, each a single merge commit with its reasoning recorded. Four decisions in this milestone carry most of the weight, and three of them are adoptions that were not optional. The state-creation gas EIP was taken whole. Twenty-five of its thirty-six files arrived already merged, including the entire gas-accounting substrate — the block gas pool, the budget type, the interpreter and the jump table — so declining it would have meant reverting all of them and re-deriving a model the conflicted files already depended on. Its behaviour is gated on a fork that is nil on every network preset, and the register records the dormancy argument in three parts: the new opcode pricing lives only in that fork's instruction set, the new gas reservoir is a no-op while state gas is zero, and the system-call allowance is gated too. The equivalence at zero state gas is exact rather than approximate, which is what made adoption safe on a consensus path. The stack refactor was likewise undeclinable, and it lands in the hottest code in the tree. Upstream replaced value-copying stack pops with pointer-returning ones, and this fork's stack is a different implementation with the same purpose. Enough call sites merged cleanly that the package could not compile without the new methods, so they were translated. Worth knowing for the next sync: this made one dispatch path fast enough to outrun a five-millisecond timer that two local tests race, which is a property of those tests rather than of the change. The BAL-based state sync protocol was declined a fourth time, and the reason has changed shape. It is no longer a question of volume but of landing site: this fork renamed its downloader and diverged it to twice upstream's size, so the four commits here that touch upstream's copy have nowhere to go. Adoption now means re-porting a two-and-a-half-thousand-line downloader onto upstream's new state machine, which is branch-and-PR work rather than merge work. The feature also remains unusable, since it exists to serve block access lists that this fork can neither produce nor build under parallel execution. The blob-cache commit was declined together with a package move, because the move exists only to let the cache share the miner's transaction selection. The cache cannot run here — the pool it wraps is declared and never assigned — and the move would have touched about twenty call sites in the block-building path to no end. Two items in the backlog are worth reading before the next sync rather than after. A container memory-limit fix was adopted, but it fixes the inherited command-line path while this fork's own server configuration still carries a near-verbatim copy of the same block, and that copy is what a normal configured startup uses; porting it changes cache sizing in production, so it is filed rather than smuggled in. And the register now notes that the deployed-code size cap has two overlapping sources here, one local and one upstream, whose interaction needs explicit boundary tests before the upstream fork is ever scheduled. The version file is deliberately not bumped. That follows the precedent from two milestones ago rather than the one from the middle of the sync, and it is not a deferral — a release tag's bump has been declined every time one appeared. What does now need an answer, and is a release decision rather than a merge one, is what that file should read for a tree whose base really is this release.
Documentation only; no code changes. Records what was run at the milestone tip after the final batch and the chores commit, and what it showed. The whole-tree test run passes 149 packages against 3 failures, and all three, along with every failing test name, are in the long-documented pre-existing set. The signatures were compared rather than just the names: the four transition-tool failures all report the same invalid eip number they have reported for many milestones, and the five console/genesis/export failures plus the devp2p conformance package all show the same nil consensus-config panic reached from the miner work loop. Worth noting the direction of travel — that pre-existing set has shrunk over this sync rather than grown, since a state-method parity test was fixed at the second milestone and two interpreter timer flakes were fixed in the penultimate batch. The linter reports no issues, which also confirms this milestone's several declines left no unused symbols behind, and both module tidies are no-ops. The vulnerability scan reports a single called advisory, in the gRPC dependency. It is inherited from the development branch rather than introduced here, and that was verified rather than asserted: the dependency is pinned to the identical version on both. The sync's entire dependency delta against that branch is seven lines, every one of them traceable to a recorded decision. The milestone's headline adoption needed an empirical check, not just an argument. Taking the state-creation gas EIP was forced by how much of it arrived already merged, and the safety case for it was analytic: with state gas at zero, which is this fork's situation everywhere while the gating fork is dormant, upstream's new exit forms reduce exactly to the previous behaviour. An analytic argument on a consensus path deserves evidence, and the tree already contains the right instrument. Replaying 241 real mainnet blocks through the merged virtual machine reproduces byte-identical state roots on both the serial and parallel paths. Since gas used is committed into receipts, receipts into the receipt root, and that root into the header, identical roots across 241 historical blocks mean identical gas. Building a second image from the pre-merge base to diff block by block was considered and is unnecessary; this is the stronger instrument and it is parameterised by real chain data. The block-access-list dormancy claim needed narrowing, and the narrowing matters at enable time. Nothing on any path here writes the header field, so it stays nil as the register has said since it arrived. But the assertion that it stays nil lives on the inherited beacon engine, which is only ever constructed around the clique or ethash engines — never on this fork's networks, whose headers are verified by its own engine, and that engine checks neither this field nor the slot-number field beside it. Harmless today, pre-existing rather than introduced here, and with no effect on state since the field is covered by the header hash. It becomes a requirement whenever the fork is scheduled, and is recorded as one. Two devnets ran on an image built from this tip, and both pass. The standard one shows both nodes advancing in lockstep with identical finalized hashes, consensus, milestones and checkpoint acknowledgements all rising, and no errors in either execution log. The witness one took two attempts, and the first failure is recorded because it is a trap rather than a typo: the sync-with-witness participant setting is not a witness toggle, it switches the node's entire sync mode to stateless, which starts from the latest checkpoint — so on a genesis-fresh chain, applying it to the block producer deadlocks the network at block zero waiting for a checkpoint only that producer could create. With production and consumption split across the two nodes it passes, and the witness path is evidenced directly rather than by log grep: the producer wrote 169 witnesses and the stateless consumer holds the identical set, the witness protocol capabilities are negotiated between the peers, and the consumer tracks the producer's head exactly — which is the end-to-end proof, since a stateless node cannot import a block without its witness. Two measurement traps are recorded alongside the results, because both nearly produced false findings. The consensus client answers two of the obvious counter endpoints with not-implemented payloads whose numeric error codes a naive parse reads as data, reporting frozen counters that are in fact climbing. And grepping execution logs for the word witness is worthless here — it returned a higher count on the broken run than on the healthy one, because the relevant logger runs on a two-minute interval. The milestone's upstream-change triage comes back empty. Each class that would need wiring on this side was checked directly rather than by reading commit subjects: the only new command-line flag and the only new config field both belong to the declined sync protocol and are absent from the tree entirely, no precompile registration moved, the generated command-line documentation regenerates byte-identically, and the generated config marshaller needs no regeneration because its input never changed.
Operator decision, taken after the final batch and the chores commit were already made. The constants now match upstream's own v1.17.4 release commit exactly; the only remaining difference from that file is a locally added comment line. The merge-time resolution had kept the previous values, and that was the right call as a *merge* decision — a release tag's bump has been declined at every milestone of this sync, and the evidence for it was re-gathered rather than trusted. What it deliberately left open was the release question the merge could not answer: what this file should read once the tree's base genuinely is this release. It now does, so that question was put and answered. The earlier reasoning is kept in the ledger rather than rewritten, because the sequence is the useful record — the merge declined the bump, the release question was raised explicitly, and the release answer was to take it. Two consequences are recorded in the ledger alongside it. The commit messages of the final batch and of the chores commit both say the version file is deliberately not bumped, and both are now out of date. Neither can be amended: rewriting either hash would break every pull request stacked above this branch, which is a far worse outcome than a stale sentence. The ledger entry is authoritative where they disagree. The change is safe but not entirely invisible, and the one place it surfaces is worth naming rather than discovering later. The default miner extra-data packs the major, minor and patch numbers into a single word, so that word moves with the patch bump, and extra-data lands in the block header. It only ever appears as content of the fixed-length vanity prefix, though, because this fork's consensus engine truncates the header's extra field to exactly the vanity length and then appends the validator bytes and seal itself. Vanity content is producer-chosen and constrained only by length, no validity rule reads it, and every real deployment configures its own. So the effect is cosmetic, and on a default-configured node it is an improvement: the advertised base version becomes the true one. Verified: the tree builds, formatting is clean, and the packages whose tests touch the version string or extra-data all pass. Nothing in the tree embeds the previous string, and the console tests derive their expectation from these same constants.
Codecov Report❌ Patch coverage is ❌ Your patch check has failed because the patch coverage (54.29%) 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.17.3-part3 #2346 +/- ##
=================================================================
+ Coverage 54.38% 54.96% +0.57%
=================================================================
Files 932 930 -2
Lines 167713 168109 +396
=================================================================
+ Hits 91211 92393 +1182
+ Misses 70724 69808 -916
- Partials 5778 5908 +130
... and 27 files with indirect coverage changes
🚀 New features to boost your workflow:
|
Final hop of the develop-drift cascade, bringing develop's #2347 (Kurtosis e2e and stateless-e2e on every PR base) and #2333 (complete witnesses under BlockSTM v2) to the top of the stack. Clean at this hop; inherits the three witness resolutions made below, at ppatil-upstream-v1.17.2, ppatil-upstream-mptubt and ppatil-upstream-v1.17.3-part3. The cascade shape matters and is worth recording: develop was merged once into the base branch as a fast-forward, then carried hop by hop up the chain. Merging develop independently into each of the eleven branches would have paid the same resolutions eleven times and put develop content into the upper diffs that is absent from their bases. Cross-check on the result: every witness-relevant file here is byte identical to a throwaway worktree in which develop was merged directly into this branch in one step and verified before the cascade began — statedb.go, reader.go, reader_witness.go, parallel_state_processor.go, v2_method_parity_test.go and the three witness test files. Eleven hops and one merge converge on the same content. Verified: build clean; vet clean apart from the pre-existing parallel_state_processor.go:341 lock-copy finding; both regeneration tests pass with 241/241 real mainnet blocks round-tripped and no skips; core/state, core/stateless and eth/protocols/wit pass in full.
Ledger section for the twelve-hop cascade that carried develop's #2333 (complete witnesses under BlockSTM v2) and #2347 (Kurtosis e2e and stateless-e2e on every PR base) up the stack: why it could not wait, why the merges cascade instead of fanning out, the single conflict and the reasoning that settled it, the three breaks git reported as clean, and the verification at each adaptation hop. Two things in it are worth more than the narrative. The first is the argument for dropping develop's `witnessStats` field rather than keeping it: upstream #34106 relocated witness statistics into `stateless.Witness`, so the field has no writer and no reader here, while the metric itself survives with identical attribution and wider coverage. The consequence — counters reading higher than on develop because prewalk-collected nodes are now counted — is recorded so nobody has to rediscover it from a dashboard. The second is the cross-check: every witness-relevant file in the finished cascade is byte-identical to a throwaway worktree where develop was merged directly into the top branch in one step, which is the evidence that no hop's resolution drifted. That check is cheap and worth repeating on any future cascade. The needs-wiring row is a genuine gap rather than a note. `ubtTrieReader` has no `CollectStateWitness` and appears in neither `findTrieReader`'s type switch nor `collectStateWitnessFromReader`'s, so a UBT-backed reader chain collects no witness at all. It predates this cascade and is dormant while UBT is off, but it fails silently rather than loudly, and the existing prewalk and read-set tests cannot see it because every case there builds an MPT reader. It needs settling before UBT is enabled, not during.
Ancestry only. The newTrieReader point-cache fix this carries was already present here, so the merge records the relationship without changing a byte. That is the reason it exists. Without it this branch would not contain its predecessor, and a stacked pull request whose head does not contain its base misreports its own diff and turns an eventual merge into an argument. Deliberately not re-verified, because there is nothing new to verify: the merge result's tree is identical to this branch's previous tree, which is the tree that already passed build, full-tree vet, #2333's prewalk and read-set tests, and CI. A merge with no tree delta cannot break what that tree established.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Final milestone of the go-ethereum v1.17.4 sync: batches 27–33, closing the target. With this the merge work for the whole sync is complete —
git merge-base --is-ancestor v1.17.4 HEADpasses, and all six milestone tags (v1.16.9,v1.17.0,v1.17.1,v1.17.2,v1.17.3,v1.17.4) are ancestors of this branch.Seven batch merge commits plus two docs commits. Every conflict resolution is recorded in
docs/upstream-merges/v1.17.4/ledger.md, every fork/EIP infork-register.md, and everything deliberately not adopted inneeds-wiring.md— nothing was dropped silently.859b9fabb, 285df1648b0, 29047e24c29, 3051aa42761, 31338ffb831, 328ce37d5ba, 33f397229eeppatil-upstream-v1.17.3-part3(#2345)The four decisions that carry the weight
EIP-8037, state-creation gas — adopted, and adoption was forced. 25 of its 36 files arrived auto-merged, including the entire gas substrate (
core/gaspool.go,core/vm/gascosts.go,interpreter.go,jump_table.go,core/state_processor.go), so declining would have meant reverting all of them and re-deriving a model the conflicted files already depended on. No new fork, no activation height, no precompile change —params/config.go, both runtime presets, both packaged genesis files andcore/forkidare untouched, andActivePrecompilesis unchanged. Behaviour is gated on Amsterdam, which is nil on every preset, and Bor'sIsAmsterdamwas already the block-based form.The safety argument is an exact equivalence rather than an approximation: with
StateGas == 0— Bor's situation everywhere while Amsterdam is dormant — upstream'sExitHalt(0)consumes precisely the remaining regular gas that Bor's previousUseGas(RegularGas)consumed,ExitRevert()preserves the budget as Bor's olderr != ErrExecutionRevertedguard did, andExitSuccess()is the identity. Bor's Ahmedabad 32 KB code-size cap survives upstream's relocation of the size check via a newevm.checkMaxCodeSizerouted into all three of its new per-branch call sites.One deletion in that commit looks alarming and is not: it removes the static-call write-protection guard from
opCreate/opCreate2, but the same commit moves the check intogasCreate/gasCreate2/gasCreateEip3860/gasCreate2Eip3860(+6 lines against −2), which the interpreter evaluates before the execution function. All four are wired in Bor's jump tables, and Bor wrapsdynamicGaserrors exactly as upstream does.snap/2 — declined a fourth time, and the reason changed shape. It is no longer volume but landing site: Bor renamed
eth/downloader/downloader.gotobor_downloader.goand diverged it to 2,570 lines against upstream's 1,252, so all four commits here touching upstream's copy (#34626, #35178, #35180, #35155) have nowhere to go. Adoption now means re-porting Bor's downloader onto upstream's snap/2 state machine plus replacingSnapSyncCommitHead, which is branch-and-PR work. The feature also remains unusable: it exists to serve block access lists, and Bor can neither produce one (AmsterdamBlocknil) nor build them under BlockSTM V2.#35156, in-place stack operations — the second forced adoption, landing in the hottest code in the tree. Upstream added seven pointer-returning stack methods; Bor's
Stackis a different implementation (a GEVM-ported fixed[1024]uint256.Intarray with a leading counter). 21 call sites ininstructions.goand 2 ineips.goauto-merged outside any conflict region, so the package could not compile without them; the seven were translated to Bor's shape.#35124, blobpool GetBlobs cache — declined as one unit with a package move. The move of
miner/ordering.go→core/txpool/txorder/exists only to let the cache share the miner's selection logic, and the cache cannot run here:eth.blobTxPoolis declared and never assigned (Bor removed the blob pool from its subpool list) and the Engine API consuming it is inherited-but-unused.Standing scans
Both clean for the last five batches running. No file under
core/stateless/oreth/protocols/wit/appears anywhere in this milestone's upstream range, and batch 33's range contains zero hunks mentioning witness at all. Bor's own witness lines did sit inside four conflict regions in batch 32, so their counts were baselined from HEAD before resolving and re-verified after — all eight files identical.Six Bor-only implementers of upstream interfaces broke outside upstream's footprint and were fixed, five of them surfaced by the build:
runEcrecoverWithCache,consensus/bor/statefull's system-call processor (a directory upstream doesn't have),eth/tracers/parity.go, aSubGas→CheckGasLegacyrename, ninecore/vmtest call sites, and aGetRulesaccessor Bor had removed entirely.Executed tests
Beyond CI's standard gates, run at this tip:
go test ./...— 149 packages pass, 3 fail, 85 carry no tests. All three failures and all twelve failing test names are in the documented pre-existing set, and the signatures were compared, not just the names:cmd/evm's four all reportinvalid eip number 1346;cmd/geth's five pluscmd/devp2p/internal/ethtestall showparams.(*BorConfig).CalculatePeriodviaminer.(*worker).newWorkLoop. That set has shrunk over this sync —TestPDBMethodParityand twocore/vmtimer flakes were fixed along the way.make lint— 0 issues, which also confirms this milestone's four declines left no unused symbols.go vet ./...andgo vet -tags integration ./...— only two pre-existing lock-copy findings, re-baselined at the previous commit and reproducing byte-identically.govulncheck— one called advisory,GO-2026-6061(google.golang.org/grpc@v1.79.3). Inherited, not introduced: grpc is pinned to the identical version onorigin/develop. The whole dependency delta against develop is seven lines, all traceable to recorded decisions.TestV2BlockSTMAllBlocksreplayed 241 real mainnet blocks through the merged EVM with byte-identical state roots on both the serial and BlockSTM-V2 paths. Since gas used commits into receipts → receipt root → header, identical roots across 241 historical blocks mean identical gas. This is the empirical counterpart to the analytic argument above.tests/bor— passes.syncmode=full, RPC consuming onsyncmode=stateless— 169 witnesses written by the producer and the identical set held by the consumer,wit/1+wit/2negotiated, consumer tracking the producer's head exactly (a stateless node cannot import a block without its witness, so head parity is the end-to-end proof).ethconfig.Configfield both belong to declined snap/2 and are absent from the tree; no precompile registration moved;make docsregenerates byte-identically;gen_config.goneeds no regeneration.Rollout notes
Not consensus-affecting as merged. Every fork and EIP in this milestone is dormant:
AmsterdamBlockis nil on every Bor preset, so EIP-8037's pricing, the EIP-7928 header field and EIP-7843'sSlotNumberare all inert, and that dormancy is argued per item infork-register.md. No coordinated upgrade is required for this branch on its own; enabling any of these forks is a separate, explicit decision with its own change, review and N-1/N/N+1 activation tests.Merge as a merge commit — never squash. Squashing rewrites SHAs and breaks every PR stacked above this one, and it would also collapse upstream's per-commit authorship, which is the point of syncing by merge.
Three items are recorded for follow-up rather than fixed here:
internal/cli/server/config.go:1522still carries a near-verbatim copy of the same block callinggopsutil.VirtualMemory(), and that is the path a realbor server --config …startup takes, so containerised nodes still size cache from host memory. Porting it changes production cache sizing, so it wants its own review.consensus/beacon, whicheth/ethconfigonly builds around clique or an ethash faker.consensus/borchecks neither field. Harmless today and pre-existing, but whoever schedules Amsterdam must add them toconsensus/bor.params.BorConfigpanic reached from the miner work loop breaks two inheritedcmd/packages and has now surfaced in three batches. CI never sees it becauseTESTALLexcludes the wholecmd/tree. Worth filing separately.Reviews are deliberately not requested yet — the team decision is to review the whole stack as one batch once every milestone is complete.