Skip to content

polygon: delete the Polygon tree, tables, protos and chain-config hooks - #23497

Merged
AskAlexSharov merged 32 commits into
mainfrom
awskii/polygon-removal-delete-tree
Aug 22, 2026
Merged

AskAlexSharov merged 32 commits into
mainfrom
awskii/polygon-removal-delete-tree

Conversation

@awskii

@awskii awskii commented Aug 22, 2026

Copy link
Copy Markdown
Member

Nothing outside polygon/ referenced it after #23495, so this deletes the tree and the hooks that existed to serve it. Last in the Polygon removal series, branched off #23495 — review the last commit only.

erigon no longer builds, reads or serves anything Polygon. Existing Polygon chaindata is rejected with a pointer to 0xPolygon/erigon rather than loaded with its consensus settings silently dropped.

Changes

  • delete polygon/ — 17 packages, ~20k lines of Go and 25.7 MB of Heimdall test fixtures
  • execution/chain: remove Config.Bor, Config.BorJSON, the BorConfig interface, BorRules, and the Bor arms of String(), getEngine() and SecondsPerSlot()
  • db/kv: remove the 13 remaining Bor* tables, the dead BorTablesCfg, and the HeimdallDB / PolygonBridgeDB labels with their table configs
  • rename kv.BorWitnesses / kv.BorWitnessSizes to kv.Witnesses / kv.WitnessSizes and gate the witness buffer on --wit-protocol instead of the chain
  • snaptype.MinBorEnum becomes MaxCaplinEnum, same value, so the on-disk enum numbering is unchanged
  • delete remote/bor.proto, its generated files, and the BorTxnLookup / BorEvents methods on ETHBACKEND; regenerate; bump EthBackendAPIVersion to 4.0.0
  • remove MimetypeBor, BorValidateHeaderTime, the Bor block-end receipt recovery in the serial and custom-trace executors, and the last Bor branches in SysCallContract and genesiswrite
  • go mod tidy drops go-merkle, ttlcache and arc/v2, which only Bor used
  • docs: remove the Polygon Bridge and Heimdall gRPC sections and the bor.* / polygon.pos.* flag reference; regenerate llms*.txt

The witness protocol

The wit/0 protocol read and wrote kv.BorWitnesses, so deleting those tables would have disabled it outright rather than removing something Polygon-specific. The protocol itself has no Polygon logic — only its table names and its buffer's chainConfig.Bor != nil gate did. The tables are renamed and the buffer now follows --wit-protocol, which is what the flag's name already implied.

That gate change is the one behaviour change here, and it is forced: there is no Bor config left to gate on. Witness ingestion is now reachable on any chain when the flag is set. No supported chain ever wrote those tables, so the rename orphans no data. If witness ingestion should stay off by default on Ethereum, that is a follow-up on the flag, not on this deletion.

Left for a follow-up

rules.EngineReader.GetPostApplyMessageFunc now returns nil from every implementation — Bor's fee-transfer logging was the only real one. Removing the hook touches four engines and two call sites in the parallel and serial executors, which does not belong in a deletion this size.

.github/workflows/scripts/test_report/generate-test-report.ts and dashboards/ keep their Polygon handling: both render historical QA results, and stripping the mappings would break display of past runs.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

# Conflicts:
#	cmd/downloader/main.go
#	cmd/integration/commands/stages.go
#	node/eth/backend.go
# Conflicts:
#	cmd/integration/commands/stages.go
…ng' into HEAD

# Conflicts:
#	cmd/integration/commands/stages.go
#	cmd/rpcdaemon/cli/config.go
#	cmd/utils/app/init_cmd.go
#	cmd/utils/app/snapshots_cmd.go
@awskii

awskii commented Aug 22, 2026

Copy link
Copy Markdown
Member Author

4-agent review over the incremental range (git diff <23495 head>..HEAD), scoped to the 65 files outside polygon/. One major, three minor. Fixed in a161860, 83800ba and a51ad9c.

Major — the renamed witness tables had no schema migration. BorWitnesses/BorWitnessSizes become Witnesses/WitnessSizes at db/kv/tables.go and are listed in ChaindataTables. For any chaindata created by an earlier binary those are new table names, and the diff touched no migration file.

MdbxOpts.Open -> openDBIs (db/kv/mdbx/kv_mdbx.go:512) takes the db.View branch under Accede/Readonly; CreateTable (:1128) gets NOTFOUND from OpenDBISimple, and if !(tx.db.ReadOnly() || tx.db.Accede()) is false, so the retry omits MDBX_CREATE and fails with db-table doesn't exists: Witnesses. erigon itself opens RW non-accede and is fine; the accede consumers are not — cmd/rpcdaemon/cli/config.go:396, cmd/integration/commands/root.go:64, cmd/downloader/main.go:777, db/kv/backup/backup.go:42, cmd/utils/app/snapshots_cmd.go:3657. With no migration registered HasPendingMigrations is false, so nothing triggers the exclusive re-open that would create the table.

Fixed by drop_bor_witness_tables, following the drop_legacy_e2_tables precedent: it drops both old buckets, and registering it is what forces the exclusive open that creates the renamed ones. No rows are copied — on main StageWitnessProcessingCfg was built only when chainConfig.Bor != nil, so every existing row is on a Polygon datadir and there is nothing to preserve. BorWitnesses/BorWitnessSizes move to *Deprecated constants in ChaindataDeprecatedTables so the live schema never recreates them on a fresh DB. DropTable is a no-op on a missing DBI, so the migration is idempotent.

Worth stating explicitly, since it is the obvious question on a PR that removes Polygon: the tables are renamed rather than deleted because they belong to the WIT side-protocol, not to Bor. p2p/protocols/wit has no Polygon references and this PR keeps the protocol, renaming --polygon.wit-protocol to --wit-protocol with the old name as an alias. What was Polygon-specific was the name and the gate — the stage is now built on witnessBuffer != nil behind --wit-protocol, default off.

Minor, fixed — Witnesses/WitnessSizes had been folded into AuRaTablesCfg with DupSort set. That config has one consumer, the Gnosis/Chiado remote KV handle in cmd/rpcdaemon/cli/config.go:1000, and AuRa reads only Epoch/PendingEpoch. The flag also contradicts the live schema: reinit() registers both tables into ChaindataTablesCfg as plain TableCfgItem{}, and Witnesses stores whole witness blobs that exceed the DupSort value limit. No reachable failure — remoteCursor.bucketCfg is assigned and never read — but two entries misdescribing the schema. Removed.

Minor, fixed — TestTxDependencyBlockDecoding (execution/types/block_test.go:47) lost GetTxDependencyTest and GetValidatorBytesTest and no longer asserts anything about tx dependency. Its remaining value is the round trip of a nested RLP payload in Extra, which TestBlockEncoding does not cover, so it is renamed TestBlockDecodingNestedRLPExtra rather than deleted.

Minor, open — the erigon init guard at cmd/utils/app/init_cmd.go:91-99 changed from a typed BorJSON check to a raw-JSON probe and has no test, unlike the identical probe in ReadChainConfig. It is the only defence on that path: types.Genesis has no bor field, so a config written past it reads back clean and the node runs under the wrong rules with the suite green.

Also swept the tip for anything the series missed and dropped five stale references in a161860: the polygon/bor and polygon/tests entries in .golangci.yml, the eleven bor_* rows in cmd/rpcdaemon/README.md, the --chain=bor-mainnet / --bor.heimdall uploader example in cmd/utils/app/README.md, the Polygon card on the docs 404 page, and a Polygon aside in cmd/downloader/readme.md.

@AskAlexSharov
AskAlexSharov added this pull request to the merge queue Aug 22, 2026
Merged via the queue into main with commit 502f1ab Aug 22, 2026
137 checks passed
@AskAlexSharov
AskAlexSharov deleted the awskii/polygon-removal-delete-tree branch August 22, 2026 10:04
lupin012 added a commit that referenced this pull request Aug 22, 2026
Resolves the fallout of the Polygon removal series (#23492..#23497) on this
branch: the Bor-specific gating added in earlier review rounds goes away with
the code it gated. borReceiptForBlock, borStateSyncLogs and
txnLookupWithBorFallback are gone with the bridge, GetTransactionReceipt is
back on the plain txnLookup, the Bor clamp in Capabilities is dropped with
chain.Config.Bor, and the Bor gating tests go with their production paths.
@lystopad

Copy link
Copy Markdown
Member

Diff +335 -1,128,683

Deleting 1M+ lines is impressive.
Screenshot 2026-08-23 at 11 10 33

Reviewing 1M+ deleted lines and clicking “Approve” is a whole different level. 🫡

github-merge-queue Bot pushed a commit that referenced this pull request Aug 27, 2026
)

Polygon has not been officially supported since 3.1, but the 3.6 docs
still offer it as a choice. This scopes it without removing the
reference material, because the bor code does still ship in this branch
— the `bor-mainnet` and `amoy` tags are selectable, the `bor` namespace
is servable, and all seven `--bor.*` / `--polygon.*` flags are
registered in `erigon --help`.

**Presented as a choice, now not:**

- Polygon sat in the Mainnets table beside Ethereum and Gnosis, with its
caveat a footnote 25 lines below. It moves to its own "Polygon (not
supported)" section, which absorbs the Amoy tag.
- Three cards advertised a one-command Polygon easy-node setup. That
guide was removed from this branch and
`/get-started/easy-nodes/how-to-run-a-polygon-node` is now a redirect,
so the cards promised something that no longer exists.
- The FAQ answered "supports networks such as Gnosis and Polygon". That
string is in `faqSchema`, so it ships as JSON-LD.

**Kept, and scoped instead:** the `bor_` method reference, the Polygon
gRPC services, and the seven flags — all accurate for 3.6. The flags
move under a "Legacy Polygon flags" heading so the CLI reference stays
complete against `erigon --help`, and the bor namespace entry and the
gRPC section point at Supported Networks.

**Two fixes along the way:** the help center cited `--bor.heimdall.url`,
which is not a flag (the real one is `--bor.heimdall`); that example now
uses `--externalcl`. The Layer 2 page description advertised "Polygon
PoS, Bor" on a page whose body is entirely about running an op-node.

`main` already dropped all of this along with the code (#23492, #23497),
so nothing here forward-ports.

`llms.txt` / `llms-full.txt` regenerated; `--check` and `npm run build`
pass.

---------

Co-authored-by: Bloxster <gianni.morselli@erigon.tech>
github-merge-queue Bot pushed a commit that referenced this pull request Aug 30, 2026
…23322)

Defects in the same family — how receipt- and log-serving endpoints
decide availability.

> **Note:** while this PR was under review, main removed Polygon support
entirely (#23492..#23497). The Bor-specific fixes made in earlier review
rounds are therefore no longer part of this PR — the history gate inside
`borReceiptForBlock` and `borStateSyncLogs`, the
`txnLookupWithBorFallback` resolution in `eth_getTransactionReceipt`,
the Bor clamp on the receipts capability, and the four Bor gating tests
were retired by the merge (34f460e) together with the code they
gated. Non-Bor behaviour is untouched: those helpers were wrappers, and
their callers now use the generic functions they wrapped (`getReceipts`,
`txnLookup`).

**1. The receipts gate consulted the wrong setting.** It read
`kvcfg.PersistReceipts`, a boolean saying the receipt cache exists on
disk, and concluded receipts were available for every block. Retention
is a separate setting: `RCacheDomain` is retired on its own
`--prune.receipts.distance` window when one is set, and alongside state
history otherwise, which is the default. It now consults
`prune.Mode.ReceiptsAmount()`, distinguishing the same three shapes
`historyRetireCutoffs` already distinguishes. Where the cache stops
covering a block, availability falls back to state history, because a
missing cache entry is not fatal: `ReadReceiptCacheV2` reports it as
absent and `GetReceipt` re-executes the block from `ReceiptDomain` plus
state history, retired at the history cutoff rather than the
RCacheDomain one. So a `--prune.receipts.distance` narrower than the
history window costs the caller nothing.

**2. Four endpoints were gated on receipts alone, though matching logs
can need more.** `eth_getLogs` and `erigon_getLogs` search `LogAddrIdx`
and `LogTopicIdx` when the query filters by address or topic; those are
standalone inverted indices retired at the history cutoff whatever the
receipt retention is, so gated on receipts they answered with an empty
log array instead of `PrunedError`. They now take the history gate on
top, but only when the filter actually reaches the indices:
`usesLogIndex` mirrors `applyFiltersV3`, which skips topic positions
that are empty because those match any topic — so `"topics": [null]`
searches nothing and stays on the receipts path, exactly like the
unfiltered form. `erigon_getLatestLogs` re-executes through a
`TraceWorker` and `overlay_getLogs` re-executes with overridden code, so
both take the history gate unconditionally.

**3. Ten endpoints serving the receipts of one block were gated on
history alone**, which both rejects blocks whose body and receipts are
present and never checks the body at all. New
`checkBlockReceiptsAvailable` composes the two boundaries: reading a
stored receipt needs the block body anyway, since the receipt carries no
`TxHash` and it is derived from the transaction. The two GraphQL
block-detail entry points had no gate; theirs is placed before the body
read, because after it a pruned body reads back as nil and the endpoint
answers "not found" before any gate could fire. The GraphQL and
Otterscan block-detail paths also select one overlay view per request
and thread it through resolution, gate, block and receipt reads, so a
background commit cannot gate one generation while another answers.

**4. The log path needs the block body for the same reason.**
`getLogsV3` reads the transaction through `TxnByIdxInBlock` for every
txNum it does not find in the in-memory cache, because `GetReceipt`
derives the receipt from it. With bodies pruned and receipts kept —
`minimal` with `--prune.receipts.distance=keep-all` — the lookup came
back empty and the loop skipped the block silently, answering with an
empty array. New `checkLogsAvailable` composes the three boundaries and
replaces the duplicated gate block in the two `getLogs`, so the
rationale is stated once.

**5. A block with no transactions could not have its receipts read at
all.** `GetReceipts` decided whether the cache had answered by testing
`len(receiptsFromDB) > 0`, which cannot tell "the cache holds nothing
for this block" from "this block has no transactions", so an empty block
fell through to `PrepareEnv` and its state-history read. Where history
is pruned, `eth_getBlockReceipts`, `debug_getRawReceipts`,
`ots_getBlockDetails` and `ots_getBlockTransactions` answered with a
leaked `ReceiptsGen: PrepareEnv: ... old data not available` instead of
an empty list. The fix belongs in the generator rather than the gate:
the gate decides availability from retention, and a block it lets
through must not leak an execution error. Availability itself is a
separate question, and the gate makes no exception for these blocks — a
transaction-free block below the cutoff is refused like any other, and
the caller gets `PrunedError` rather than an empty list that cannot be
told apart from "no receipts". So this fix covers the blocks the gate
serves: receipts retained, archive, or above the cutoff. The two
boundaries are the whole gate, so `oldestBlock` in `eth_capabilities` is
the exact boundary the endpoints honour rather than a floor with holes
below it.

**6. The blocks gate read the chain-history-expiry sentinel as "nothing
is pruned".** `KeepPostMergeBlocksPruneMode` is a policy, not a window:
on a chain declaring a merge point, pre-merge transaction segments are
never downloaded, so `checkPruneBlocks` now refuses below `MergeHeight`,
and `eth_capabilities` resolves its `blocks` boundary through the same
function, so the two cannot diverge. A legacy archive datadir persists
the same blocks sentinel, so the datadir decides rather than the stored
retention — and neither a pre-merge body nor the oldest available block
can: expiry keeps pre-merge headers and bodies, and the transaction
segment spanning the merge point reaches below it. The gate reads a
pre-merge transaction, sampled by halving the range so the candidates
stay clear of the transaction segment spanning the merge point; where
the chain carries no pre-merge transaction at all, the cumulative txnum
position of the last pre-merge body says so and nothing is left to be
missing. The verdict is availability rather than policy, so it is cached
for a short TTL in both directions instead of being settled once, and
availability widening while segments arrive reopens the gate within that
window. The remote rpcdaemon's block reader answers `FrozenBlocks`
through a new ethbackend RPC instead of panicking, so the receipt gates,
`eth_capabilities` and the receipt generator keep consulting the real
value everywhere.

**7. `erigon_getLogsByHash` consulted its receipt cache before any
gate.** A receipt set cached while its block was inside the retention
window stayed servable after the boundary moved past it — holes below
the advertised `oldestBlock`, the defect class this PR removes. The gate
now runs before the cache, so a hit is gated like a miss, at the cost of
one read-only tx on cache hits.

This covers the audit asked for in point 2 of #22260 for
`erigon_receipts.go` and `otterscan_block_details.go`: migrated to the
correct boundary rather than documented as genuinely needing history.

## Coverage

`prune_gating_test.go` pins **34 endpoints across the nine prune mode
shapes** on an old and a recent block: 612 cells.
`check_prune_gates_test.go` covers the gates directly — the boundary
block itself, the boundary named in each error, the archive short
circuit, each receipt retention shape including a window wider and a
window narrower than history, each leg of both composed gates, and
`usesLogIndex` over the criteria shapes. `checkTxFee` gains the unit
test it never had, so every `check*` in the package now has one. It also
pins the datadir shapes behind the blocks sentinel — aligned segments,
bodies without transactions, a chain with no pre-merge user transaction,
a sampled block without transactions, blocks arriving later, and a
stored history retention that does not change the verdict. The remote
block reader's frozen-block refresh is covered directly: a failed fetch
is retried instead of being served as fresh, and a slow one delays only
the goroutine that fetches.

## Verified on a live node

Sepolia at tip with `--prune.mode=minimal --prune.include-receipts` —
receipts on disk, block bodies pruned. Head 11500055, both boundaries at
11400054, old block probed 5750027. **35 checks, all passing.**

| Family | Endpoints | Old block | Why |
|---|---|---|---|
| headers | `erigon_getHeaderByNumber`, `debug_getRawHeader` | served |
headers are never pruned |
| block data | `eth_getBlockByNumber`,
`eth_getBlockTransactionCountByNumber`,
`eth_getUncleCountByBlockNumber`, `debug_getRawBlock` | refused | bodies
are pruned in `minimal` |
| receipts of one block | `eth_getBlockReceipts`,
`debug_getRawReceipts`, `erigon_getBlockReceiptsByBlockHash`,
`erigon_getLogsByHash`, `ots_getBlockDetails`,
`ots_getBlockTransactions` | refused, naming `blocks are available` |
the blocks leg this PR adds — receipts are present, the body is not |
| logs by filter | `eth_getLogs`, `erigon_getLogs` | refused, naming
`history is available` | log indices follow history, not receipts |
| state / trace | `eth_getBalance`, `trace_block`,
`debug_traceBlockByNumber`, `ots_hasCode` | refused | history is pruned
|

This table was measured before the log gate took the blocks leg
described in point 4. On the current revision its two log rows refuse on
the blocks boundary rather than the history one, because `minimal`
prunes bodies and that leg is checked first; the rows above and below
are unaffected. The blocks leg is covered end to end by the unit tests,
not by a live probe.

Every recent-block probe is served. The two middle rows name **different
boundaries** for the same block, which is what this configuration shows:
before the change both said `history is available`.

Note that in `minimal` the receipt endpoints refuse either way, so only
the named boundary changes. The behavioural flip — refused becoming
served — happens where bodies are kept and receipts outlive history
(`blocks` with `--prune.receipts.distance=keep-all`), which is the
largest group of the 26 red cells above.

## Second live node: `blocks` with receipts kept

Sepolia at tip with `--prune.mode=blocks --prune.include-receipts
--prune.receipts.distance=keep-all` — every body and every receipt on
disk, state history pruned below block 11238830. Old block probed:
5750484, some 5.5M blocks below the history boundary.

| Family | Endpoints | Old block | Why |
|---|---|---|---|
| headers | `erigon_getHeaderByNumber`, `debug_getRawHeader` | served |
never pruned |
| block data | `eth_getBlockByNumber`,
`eth_getBlockTransactionCountByNumber`,
`eth_getUncleCountByBlockNumber`, `eth_getTransactionByHash` | served |
bodies are kept |
| **receipts of one block** | `eth_getBlockReceipts`,
`debug_getRawReceipts`, `eth_getTransactionReceipt`,
`erigon_getBlockReceiptsByBlockHash`, `erigon_getLogsByHash`,
`ots_getBlockDetails`, `ots_getBlockTransactions` | **served** | body
and receipts are both there — on `main` all seven refuse |
| **logs, unfiltered** | `eth_getLogs`, `erigon_getLogs` | **served**,
125 logs | read straight from receipts; no index is consulted |
| **logs, filtered by address** | `eth_getLogs`, `erigon_getLogs` |
**refused**, naming `history is available` | the index search needs
history — on `main` both return an empty array |
| state / trace | `eth_getBalance`, `trace_block`,
`debug_traceBlockByNumber`, `ots_hasCode` | refused | history is pruned
|
| block data, follow-up | `debug_getRawBlock`, `debug_getRawTransaction`
| refused | still gated on history; see Follow-up |

This is the configuration where the change is behavioural rather than a
difference in wording. `eth_getBlockReceipts` returns 101 receipts for a
block whose state history is long gone, `ots_getBlockDetails` computes
its `totalFees`, and `erigon_getLogsByHash` returns all 101 log
positions — none of which `main` will answer. In the other direction a
filtered `eth_getLogs` now refuses instead of quietly returning `[]`,
while the unfiltered form still answers with its 125 logs.

36 of 38 checks pass; the two failures are `debug_getRawBlock` and
`debug_getRawTransaction`, whose fix is in the follow-up PR.

### Topic shapes on the same node

The empty-topic-position case, probed on block 5750932 of the same
datadir with an rpcdaemon opened read-only:

| filter | before | after |
|---|---|---|
| no `topics` | 1117 logs | 1117 logs |
| `"topics": []` | 1117 logs | 1117 logs |
| `"topics": [null]` | `PrunedError` | 1117 logs |
| `"topics": [[]]` | `PrunedError` | 1117 logs |
| `"topics": [null, null]` | `PrunedError` | 1113 logs |

The last row keeps 1113 rather than 1117 because that shape requires two
topic positions to match, so it drops the four logs carrying a single
topic; the filter is working, the gate is not involved. A query filtered
by address still refuses naming `history is available`, and the
single-block receipt endpoints stay served, so the blocks leg added in
point 4 introduces no false refusal where bodies are kept.

## Gate boundaries measured against the data

Each gate names a boundary; a second `rpcdaemon` built with
`checkPruneField` short-circuited to `nil` says where the data actually
starts. Both were opened read-only on the same Sepolia `blocks` datadir
— head 11501094, declared boundary 11238950 — and the first block that
answers correctly was found by bisection, with a block's own receipts as
the ground truth for what its log endpoints must reproduce.

| gate | endpoints | declared | first block that answers | verdict |
|---|---|---|---|---|
| `checkPruneHistory` | `eth_getBalance`, `eth_getCode` | 11238950 |
11163680 | conservative by 75270 |
| `checkPruneHistory` | `trace_block`, `debug_traceBlockByNumber` |
11238950 | 11163681 | conservative by 75269 |
| index leg of `checkLogsAvailable` | `eth_getLogs`, `erigon_getLogs`
filtered | 11238950 | 11163681 | conservative by 75269 |
| `checkBlockReceiptsAvailable` | `eth_getBlockReceipts`,
`debug_getRawReceipts`, `ots_getBlockDetails`,
`ots_getBlockTransactions` | never refuses | complete at every block
with transactions from 800000 to the tip | exact |
| receipts leg of `checkLogsAvailable` | `eth_getLogs`, `erigon_getLogs`
unfiltered | never refuses | correct at every block with logs | exact |

The 75k-block gap is not a defect: file retirement is floored to the
file step, so data survives below the declared boundary until the next
prune pass removes it. The gate promises the boundary the configuration
guarantees rather than the physical one that moves at every retire,
which is the pre-existing behaviour of `checkPruneField`.

The index leg is where the change earns its place. Filtering the same
block by an address taken from its own logs:

| block | gate removed | gate in place |
|---|---|---|
| 5750932 | 0 of 1 expected log | refused |
| 11163680 | 0 of 1 expected log | refused |
| 11163679 | 0 of 1 expected log | refused |
| 11238949 | 35 logs, correct | refused |
| 11238950 | 116 logs, correct | 116 logs |

Below the physical floor the filtered query answers **silently zero**,
which is what the history leg turns into `PrunedError`. The physical
floor of the log index, 11163681, coincides with that of state history,
11163680 — the premise this PR rests on, measured rather than assumed.
The unfiltered form is correct at every block probed: 1117, 1016, 1360,
525, 443, 1237 and 216 logs, each matching its receipts, identical with
and without the gate.

Point 5 was found the same way: blocks 1000, 1000000, 1510087 and
2000000 go from the `PrepareEnv` error to an empty list, while blocks
carrying transactions are unchanged — 800000 keeps 1 receipt, 1511431
keeps 4, 5750932 keeps 99, 11501058 keeps 94.

The blocks leg is the one this datadir cannot exercise, since it keeps
every body; it stays covered by the unit tests.

## Follow-up

The endpoints that need the block body but not the receipts are left to
a second PR: `debug_getRawBlock`, `debug_getRawTransaction` and
`erigon_getBlockByTimestamp` still gate on history, so under
`--prune.mode=blocks` they refuse bodies that were never pruned — the
defect class #21965 reported. That PR also gates `eth_feeHistory`, which
has none today, and completes the table with the header, trace and
Otterscan search endpoints.
bloxster pushed a commit that referenced this pull request Sep 1, 2026
Eight docs changes landed on release/3.6 between 2026-08-20 and 08-28
and were never forward-ported. Each claim here was re-derived from
main's own source rather than carried over, and three of them turned out
to be false on main.

Corrected against main rather than ported:

- eth_getFilterLogs no longer resets a filter's eviction deadline.
  #23296 rewrote it to read the stored criteria and serve them through
  the eth_getLogs path, which dropped the TouchSubscription call that
  release/3.6 still makes. On main only eth_getFilterChanges counts as a
  poll, so a client polling exclusively with eth_getFilterLogs loses its
  filter after five idle minutes.
- BlockNumberOrHash.UnmarshalJSON has a top-level "latestExecuted" case
  on main, so the bare string works on eth_call and friends. On
  release/3.6 only the object-wrapped form does. "null" is still
  top-level-only on plain block-number parameters.
- eth_fillTransaction rejects three inputs on main that release/3.6
  accepts: gasPrice together with authorizationList, an empty
  authorizationList, and a derived maxFeePerGas or maxFeePerBlobGas that
  overflows 256 bits.

Also adapted: the gasBailOut burn-contract note drops the Bor chains and
the replay-path note drops Bor state-sync transactions, both gone with
the Polygon tree in #23497.

Ported after verifying the behaviour is unchanged on main:

- The state-cache environment variables. The four STATE_CACHE_* budgets,
  USE_STATE_CACHE and DISABLE_ADAPTIVE_PIN carry the same defaults here,
  and the "new in v3.6" comparisons check out against release/3.5
  (accounts 1GB -> 150MB, code index 16MB -> 32MB, no pin controller).
- eth_getWitness and eth_getTxWitness, including the genesis and
  empty-access-set early returns that skip the root self-check, and the
  int(txIndex) narrowing.
- trace_block and trace_replayBlockTransactions withdrawals, and what
  gasBailOut does to replayed balances.
- Caplin block production since v3.6: payload preparation one slot
  ahead, the head published before the head-state copy, and the default
  client-pair graffiti. payload_preparation.go is identical on the two
  branches.
- Caplin no longer claimed to remove disk storage -- it keeps an
  indexing DB, its own snapshots and further subdirectories under the
  same datadir. What it removes is the second process and second
  datadir.
- --prune.include-receipts is sticky per datadir, and on its own does
  not reach genesis: the receipt cache follows the state-history window,
  and keep-all retains the cache domain but not the log address and
  topic indexes a filtered eth_getLogs needs.
- --externalcl is a switch, not an address. It disables Caplin, after
  which the external client dials in to the Engine API.

The llms.txt cross-link on why-using-erigon is deliberately left out --
it belongs to #23336.
pull Bot pushed a commit to Dustin4444/erigon that referenced this pull request Sep 1, 2026
…rigontech#23595, erigontech#23625 to main (erigontech#23676)

Ports five docs PRs merged to `release/3.6` between 2026-08-20 and
08-28. Every claim was re-derived against `main`'s own source rather
than copied across — three did not survive that check, and two needed
adapting.

| Ported | Brings |
| --- | --- |
| erigontech#23359 | The state-cache environment variables |
| erigontech#23587 | Caplin block production since v3.6, and the disk-storage
claim in the Caplin intro |
| erigontech#23593 | Pruning Modes: receipt-cache stickiness, `keep-all`, snapshot
reclaim |
| erigontech#23595 | `eth.md` JSON-RPC deviations and `trace.md` withdrawals /
`gasBailOut` |
| erigontech#23625 | The `--externalcl` correction and the Layer 2 page
description |

### Written differently here, because release/3.6 is wrong for main

- **`eth_getFilterLogs` no longer resets a filter's eviction deadline.**
erigontech#23296 rewrote it to read the stored criteria and serve them through the
`eth_getLogs` path, dropping the `TouchSubscription` call `release/3.6`
still makes. A client polling only with `eth_getFilterLogs` loses its
filter after five idle minutes. This documents it as it is, but it reads
like an unintended side effect of erigontech#23296 rather than a deliberate
change.
- **`BlockNumberOrHash.UnmarshalJSON` has a top-level `"latestExecuted"`
case on `main`**, so the bare string works on `eth_call` and friends. On
`release/3.6` only the object-wrapped form does.
- **`eth_fillTransaction` rejects three inputs on `main`** that
`release/3.6` accepts: `gasPrice` with `authorizationList`, an empty
`authorizationList`, and a derived `maxFeePerGas` or `maxFeePerBlobGas`
that overflows 256 bits.
- **The state caches do not start at a flat 1024 entries.** Start is
`max(1024, shards × 16)` bounded by the ceiling, with the shard count
following `min(ceiling/64, GOMAXPROCS × 16)` rounded to powers of two.
`minShardStart` does not exist on `release/3.6` at all.

### Adapted for the Polygon removal (erigontech#23497)

The `gasBailOut` burn-contract note drops the Bor chains, and the
replay-path note drops Bor state-sync transactions.

### Verified unchanged, then ported as written

`eth_getWitness` / `eth_getTxWitness` including the genesis and
empty-access-set early returns; the withdrawals `stateDiff` shapes;
Caplin payload preparation, head publication and default graffiti
(`payload_preparation.go` is byte-identical across the branches); the
`--prune.include-receipts` stickiness warning; and the `STATE_CACHE_*`
defaults, whose "new in v3.6" comparisons check out against
`release/3.5`.

The llms.txt cross-link on `why-using-erigon` is deliberately left out —
it belongs to erigontech#23336.

Follows erigontech#23675. Gate: `npm ci && npm run build` green, `generate-llms.py
--check` OK (72 pages), `render-disk-sizes.py --check` OK, editorial
scan and `sidebar_position` lint clean.

---------

Co-authored-by: Bloxster <gianni.morselli@erigon.tech>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants