# Geth vs spec: EIP-2780 intrinsic gas, EIP-7976 calldata floor, EIP-7981 access-list cost (Amsterdam)

Batch `el-intrinsic`, item `geth`. This is a review of public code only. No node or network was run against.
Amsterdam/Glamsterdam is not live on mainnet, so the live-code withholding rule does not apply to anything below.

## 1. Pins

| Repo | Branch | Commit (from `api.github.com/repos/<repo>/commits/<branch>`, 2026-10-27) | Source |
|---|---|---|---|
| ethereum/go-ethereum | master | `8817f99f92852f8ac53e596de6202dd9e3494065` (committed 2026-10-27T03:32:47Z) | shallow clone, `git rev-parse HEAD` matches |
| ethereum/execution-specs | forks/bogota (default) | `7f2f4c9bf6d73b09ecb8b24eb14d08bf0cf0247b` (2026-10-27T17:40:55Z) | shallow clone, matches |
| NethermindEth/nethermind | master | `d48833af88ec4224c86afadd1c596e8f619c406b` (2026-10-27T12:00:20Z) | shallow clone, matches |
| ethereum/EIPs (EIP text) | master | `d4c575521d99ec881a444ab3af604e684b5f0743` | raw.githubusercontent.com |

Citation bases (all `path#L` refer to these pins):
- Geth: `https://raw.githubusercontent.com/ethereum/go-ethereum/8817f99f92852f8ac53e596de6202dd9e3494065/<path>`
- Spec: `https://raw.githubusercontent.com/ethereum/execution-specs/7f2f4c9bf6d73b09ecb8b24eb14d08bf0cf0247b/src/ethereum/forks/amsterdam/<path>`
- Nethermind: `https://raw.githubusercontent.com/NethermindEth/nethermind/d48833af88ec4224c86afadd1c596e8f619c406b/src/Nethermind/<path>`

Status of the three EIPs at the EIPs pin: EIP-2780, EIP-7976 and EIP-7981 are all `status: Last Call`, `last-call-deadline: 2026-11-01`.

Note: the execution-specs tracker issue #3217 names `forks/amsterdam` as its EELS branch. As the brief instructs, this review uses the default branch `forks/bogota`, path `src/ethereum/forks/amsterdam/`.

## 2. Candidates

**None.** I found no place where Geth's intrinsic gas, calldata floor, access-list charge, check order or floor settlement gives a different gas figure, state or block-validity result from the spec at the pins. Following the brief ("prefer no candidate to a weak one"), the near-misses I examined and rejected are listed in §4 with the reason for each.

How I tried to disprove the "no candidate" result:
1. **Line-by-line reading** of Geth `IntrinsicGas`, `intrinsicBaseGasEIP2780`, `FloorDataGas`, `execute`, `preCheck` and `settleGas` against spec `calculate_intrinsic_cost`, `validate_transaction`, `check_block_gas_capacity` and `settle_transaction_gas` (§3).
2. **Differential vector check, run locally.** This ran no node and no network. A throwaway Go test in a temporary copy of the pinned Geth clone (go1.25.4) called `core.IntrinsicGas` and `core.FloorDataGas` with Amsterdam rules on 3000 random transactions. The cases were:
   - create, self-transfer and call to another account;
   - value 0 or 1;
   - no access list, or 0–3 entries with 0–3 keys each;
   - 0–3 authorizations;
   - calldata from 0 to 70 000 bytes, about one-third zero bytes.

   I compared the outputs with a Python transcription of spec `transactions.py#L689-L798` and the `vm/gas.py` constants: **0 mismatches** in either the execution intrinsic or the floor. The harness was not delivered. Limitation: the Python side is my own transcription, not the EELS package (its dependencies were not installed).
3. **Public test results.** In the hive Glamsterdam `eels/consume-engine` run for Geth `v1.17.11-unstable-d01e869d-20261026` (file `1793010480-d551c775c4d248e574860df4df37a25e.json`, listed in `listing.jsonl`), every test case whose name contains these EIPs passed:

   | EIP | Passed | Failed |
   |---|---|---|
   | eip2780 | 292 | 0 |
   | eip7976 | 553 | 0 |
   | eip7981 | 194 | 0 |
   | eip7623 | 966 | 0 |
   | eip8038 | 274 | 0 |

   That run's only 2 failures are EIP-7928 BAL cross-index tests. The `consume-enginex` run's 50 failures are 4844/7918 BPO fork-transition tests plus the same 2 EIP-7928 tests. Caveat: hive tested `d01e869d` (2026-10-26), not exactly the pin `8817f99f`.

## 3. Rules checked and found correct

Spec = execution-specs at the pin. "Agrees" = Geth gives the same result as the spec.

| # | Rule (spec) | Spec location | Geth location at pin | Nethermind at pin | Result |
|---|---|---|---|---|---|
| 1 | `TX_BASE = 12000`, `TX_VALUE_COST = 6000` | `vm/gas.py#L143-L144` | `params/protocol_params.go#L111-L112` (`TxBaseCost2780 = 12000`, `TxValueCost2780 = 6000`) | `Nethermind.Core/GasCostOf.cs#L100-L101` | agrees |
| 2 | Recipient term: self-transfer 0; create `CREATE_ACCESS` (=9000+3000); otherwise `COLD_ACCOUNT_ACCESS` 3000 | `transactions.py#L739-L747`, `vm/gas.py#L77,L85,L90` | `core/state_transition.go#L163-L184`; `params/protocol_params.go#L115,L117,L123` | `Nethermind.Evm/GasPolicy/EthereumGasPolicy.cs#L779-L780,L845-L850,L865-L880` | agrees |
| 3 | Value term only for a non-self, non-create call with value > 0 (**creation with value pays no `TX_VALUE_COST`**) | `transactions.py#L746-L747` | `core/state_transition.go#L187-L192` | `EthereumGasPolicy.cs#L869-L877` | agrees |
| 4 | Self-transfer test is `tx.to == sender` (recovered sender) | `transactions.py#L737` | `core/state_transition.go#L166` (`*to == from`); `from` is `msg.From` (L729) and recovered `from` in the txpool (`core/txpool/validation.go#L120,L135`) | `EthereumGasPolicy.cs#L779-L780` | agrees |
| 5 | Calldata standard cost `tokens*4` with tokens = zero + 4·nonzero (= 4/16 per byte) | `transactions.py#L732-L734,L801-L811` | `core/state_transition.go#L93-L112` (`TxDataZeroGas=4`, `TxDataNonZeroGasEIP2028=16`, `params/protocol_params.go#L39,L93`) | `IntrinsicGasCalculator.cs#L46-L51`, `EthereumGasPolicy.cs#L853-L854` | agrees |
| 6 | Init-code word cost `2*ceil(len/32)` on create only | `transactions.py#L741`, `vm/gas.py#L864-L883` | `core/state_transition.go#L114-L120` (`InitCodeWordGas=2`, `params/protocol_params.go#L46`) | `GasCostOf.cs#L59` (via `GetBaseDataCost`) | agrees |
| 7 | Access-list entry cost 2900/address, 2000/key (`COLD-WARM`, EIP-8038) | `vm/gas.py#L147-L152` | `core/state_transition.go#L128-L141`; `params/protocol_params.go#L124-L125` | `IntrinsicGasCalculator.cs#L59-L74`, `Nethermind.Core/Eip8038Constants.cs#L10` | agrees |
| 8 | EIP-7981 surcharge: 80 tokens/address, 128/key, ×16 = 1280/2048, added to the **execution** intrinsic | `transactions.py#L69,L77,L750-L766,L789-L791` | `core/state_transition.go#L144-L157` (20·16·4 = 1280; 32·16·4 = 2048) | `IntrinsicGasCalculator.cs#L54-L57,L74` | agrees |
| 9 | Same surcharge also in the **floor** (both sides of the max) | `transactions.py#L781-L786` | `core/state_transition.go#L212-L225` (tokens += 80·addr + 128·key) then `floorBase + tokens*16` (L251-L255) | `IntrinsicGasCalculator.cs#L164` | agrees |
| 10 | EIP-7976 floor: `len(data)*4` tokens at 16 = 64/byte, zero and nonzero alike | `vm/gas.py#L145-L146`, `transactions.py#L775,L784` | `core/state_transition.go#L202-L210` (`TxCostFloorPerToken7976=16`, `params/protocol_params.go#L96`) | `IntrinsicGasCalculator.cs#L105-L106,L164` | agrees |
| 11 | Floor base = `TX_BASE + recipient term (+value)`; init-code and auth excluded | `transactions.py#L779-L786` | `core/state_transition.go#L246-L249` (`intrinsicBaseGasEIP2780`) | `EthereumGasPolicy.cs#L798` (`baseCost + createCost + eip2780ExtraGas`; init code is in `DataCost`, not `createCost`) | agrees |
| 12 | EIP-7702: per-auth `EXECUTION_PER_AUTH_BASE_COST` = 101·16+3000+3000+200 = 7816 | `vm/gas.py#L157-L162`, `transactions.py#L768-L772` | `core/state_transition.go#L85-L91`; `params/protocol_params.go#L108` (`7816`) | `IntrinsicGasCalculator.cs#L81-L98`, `Eip8038Constants.cs#L26` | agrees |
| 13 | Set-code tx with empty auth list or `to = nil` is invalid | `transactions.py#L664-L670` | `core/state_transition.go#L670-L681` | — | agrees |
| 14 | Validity: `intrinsic > gas` → invalid; `floor > gas` → invalid; `intrinsic > TX_MAX_GAS_LIMIT` or `floor > TX_MAX_GAS_LIMIT` → invalid; `gas > 2^32-1` → invalid | `transactions.py#L640-L641,L672-L684`; `vm/gas.py#L153-L154` | `core/state_transition.go#L729-L754` (intrinsic, floor, `max(intrinsic,floor) > MaxTxGas`); `#L590-L594` (`MaxTxTotalGas = 1<<32-1`, `params/protocol_params.go#L31-L32`) | `Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs#L1040-L1064` | agrees (the `skipExecutionGasCap` bypass, L321-L327, is a simulation-only `Message` flag) |
| 15 | Block capacity check: `min(TX_MAX_GAS_LIMIT, tx.gas)` vs execution room, `tx.gas` vs state room | `vm/gas.py#L1038-L1086` | `core/state_transition.go#L689-L696`, `core/gaspool.go#L64-L72` | — | agrees |
| 16 | Refund capped at `before_refund/5`, **floor applied after refund**: `gas_used = max(after_refund, floor)`; block execution gas = `max(before_refund - state_gas, floor)` | `vm/gas.py#L1163-L1228` | `core/state_transition.go#L1066-L1092`, `#L1238-L1249` | `TransactionProcessor.cs#L1955-L1978` (`Math.Max(operationGas, floorGasLong)`) | agrees |
| 17 | Boundary where floor == execution cost: no adjustment (`max` of equals) | `vm/gas.py#L1214` | `core/state_transition.go#L1078` (strict `<`, so equal values leave gasUsed unchanged, the same result) | `Math.Max` | agrees |
| 18 | Fork activation by timestamp at the first Amsterdam block | (fork scheduling) | `params/config.go#L742-L744`, `#L1308` (`IsAmsterdam: isMerge && c.IsAmsterdam(num, timestamp)`) | `Nethermind.Specs/Forks/25_Amsterdam.cs#L14-L16,L25` | agrees |
| 19 | txpool and block processing use one formula | — | the txpool calls the same `core.IntrinsicGas` and `core.FloorDataGas`, plus the same `max(...) > MaxTxGas` cap (`core/txpool/validation.go#L135-L157`); t8n does too (`cmd/evm/internal/t8ntool/transaction.go#L143,L157`) | — | agrees (see the fork-boundary note in §4) |
| 20 | Protocol minimum-gas filters after Amsterdam use 12000 | — | `miner/worker.go#L466-L471`; `eth/protocols/eth/peer.go#L638-L643` (PR #36020, the pin commit itself) | — | consistent: per-tx gas used ≥ `TX_BASE` = 12000 |

## 4. Rejected near-misses (not candidates)

- **Check order (cosmetic only).** Spec `check_transaction` calls `validate_transaction` (intrinsic and floor) before the nonce and balance checks (`fork.py#L538-L571`). Geth runs `preCheck` first (nonce, caps, fees, `buyGas`; `core/state_transition.go#L573-L698`) and the intrinsic check after it (L729-L754). Any transaction failing either is invalid in both, so a block containing it is rejected by both. Only the reported error class can differ when a transaction fails several checks. hive EEST exception matching passes for these EIPs (§2), so this was not escalated.
- **`accessList != nil` vs spec `has_access_list(tx)`.** An empty or absent list contributes 0 either way, so the result is identical. The same holds for `authList != nil`: an empty set-code list is rejected (#13).
- **uint64 overflow errors** (`ErrGasUintOverflow`) versus the spec's unbounded `Uint`. These are unreachable, because any value that large far exceeds `TX_MAX_TOTAL_GAS_LIMIT` and is invalid in both.
- **txpool at the fork boundary (not consensus).** `ValidateTransaction` derives `rules` from the pool's current head (`core/txpool/validation.go#L74`, called with `pool.currentHead.Load()` at `core/txpool/legacypool/legacypool.go#L561`). Until the first Amsterdam block is the head, the pool applies Osaka pricing. Two effects follow:
  - a 12000–20999-gas self-transfer is refused;
  - an access-list transaction priced for Osaka (2400/1900, no surcharge) can be admitted although it is below the Amsterdam intrinsic gas.

  The block builder re-executes under Amsterdam rules and skips such a transaction, so no invalid block results. This is the usual Geth fork-transition pool behaviour, not a spec rule.
- **Gas estimator (RPC only, not consensus).** `eth/gasestimator/gasestimator.go#L61` still treats a caller-supplied gas below 21000 as "unset" (`call.GasLimit >= params.TxGas`). After Amsterdam a 12000-gas self-transfer is valid, so a cap between 12000 and 20999 is ignored as the upper bound. L127 runs the plain-transfer short-circuit at 21000 and falls back to binary search on failure. This affects RPC behaviour only.
- **EIP prose vs spec code (not a Geth issue).**
  - EIP-2780's EIP-7623 paragraph says the floor base adds "`COLD_ACCOUNT_ACCESS` and `TX_VALUE_COST` for a value transfer to a different account". This could be read as excluding `COLD_ACCOUNT_ACCESS` for a zero-value call, but its reference-cases table and the spec code (`transactions.py#L742-L747,L779`) charge it on every non-self call. Geth follows the code.
  - EIP-7981 prose says "flat surcharge … outside the max". The spec adds it to both sides of the max (`transactions.py#L762-L766`), which is numerically identical. Geth follows the code.
- **Out of scope, for the EIP-8037 item, not assessed.** Spec `settle_transaction_gas` clamps a negative net state gas to 0 (`vm/gas.py#L1216`, docstring: "refunds can drive it negative"). Geth instead returns an error when the top-frame `UsedStateGas < 0` (`core/state_transition.go#L1047-L1049`), which makes the transaction invalid. Whether net-negative top-frame state gas is reachable in Geth was not examined. This is a lead for the state-gas reviewer, not a candidate here.

## 5. Known-check

GitHub search API (`api.github.com/search/issues`, issues and PRs, open and closed), `repo:ethereum/go-ethereum`:

| Query | Relevant hits (number, state) |
|---|---|
| `2780` | #35212 implement EIP-2780 (closed PR); #35318 EIP-2780 + EIP-8037 changes (closed PR); #35454 / #35456 update 2780 / 8038 parameters (closed PRs); #35467 miner EIP-2780 packing floor (closed PR); #35592 gasestimator plain transfer (closed PR); #35773 exclude txs if gas insufficient (closed PR); #35279 remaining spec fixes for glamsterdam (closed PR) |
| `7976` | #34748 implement eip-7976 (closed PR) |
| `7981` | #34755 implement eip-7981 (closed PR) |
| `calldata floor`, `"floor" amsterdam` | #35954 report EIP-8037 gas settlement in tracers (open PR); #33601 EIP-8037 (closed PR) |
| `intrinsic amsterdam` | #35838 eth_call execution gas capped at 2^24 after Amsterdam (closed issue) |
| `"access list" amsterdam` | only BAL / EIP-7928 items, none about the EIP-2930/7981 intrinsic charge |
| `"minimal gas"` | #36020 "eth/protocols/eth: fix minimal gas after amsterdam", closed and merged 2026-10-27T03:32:48Z (= the pin commit; already indexed) |

- execution-specs `"Test Release Tracker"`: #3217 `tests@v21.0.0` (Glamsterdam Testnet), open. Its EIP-2780 and EIP-7976 entries list only EIP-text wording PRs (ethereum/EIPs#11377, #11226), no client issues. I also saw #3518 `glamsterdam-devnet@v8.1.4` (closed) and #3889 `bogota-devnet@v2.0.0` (open).
- `https://hive.ethpandaops.io/glamsterdam/listing.jsonl`: the latest Geth runs (2026-10-26) show consume-engine 44223/44225, consume-enginex 83208/83258, consume-rlp 44159/44161 and execute 173/173. No failure falls in eip2780, eip7976, eip7981, eip7623 or eip8038 (§2).
- With no candidates, there is nothing to mark "known". The queries above found no open Geth issue on these rules.

## 6. URLs read

- https://api.github.com/repos/ethereum/go-ethereum/commits/master
- https://api.github.com/repos/ethereum/execution-specs/commits/forks/bogota
- https://api.github.com/repos/NethermindEth/nethermind/commits/master
- https://api.github.com/repos/ethereum/EIPs/commits/master
- https://github.com/ethereum/go-ethereum (shallow clone, depth 1)
- https://github.com/ethereum/execution-specs (shallow clone, branch forks/bogota)
- https://github.com/NethermindEth/nethermind (shallow clone, depth 1)
- https://raw.githubusercontent.com/ethereum/EIPs/master/EIPS/eip-2780.md (text of https://eips.ethereum.org/EIPS/eip-2780)
- https://raw.githubusercontent.com/ethereum/EIPs/master/EIPS/eip-7976.md (https://eips.ethereum.org/EIPS/eip-7976)
- https://raw.githubusercontent.com/ethereum/EIPs/master/EIPS/eip-7981.md (https://eips.ethereum.org/EIPS/eip-7981)
- https://raw.githubusercontent.com/ethereum/EIPs/master/EIPS/eip-8038.md
- https://api.github.com/search/issues (queries in §5)
- https://api.github.com/repos/ethereum/go-ethereum/pulls/36020
- https://api.github.com/repos/ethereum/execution-specs/issues/3217
- https://hive.ethpandaops.io/glamsterdam/listing.jsonl
- https://hive.ethpandaops.io/glamsterdam/results/1793010480-d551c775c4d248e574860df4df37a25e.json
- https://hive.ethpandaops.io/glamsterdam/results/1793002245-b76d34132a39972f8bd764357b16c253.json
- https://go.dev/dl/go1.25.4.linux-amd64.tar.gz (local toolchain for the vector check only)

Files read at the pins:
- Geth:
  - `core/state_transition.go` (L60-L275, L315-L330, L520-L800, L1040-L1250)
  - `params/protocol_params.go` (L25-L140)
  - `core/txpool/validation.go` (L60-L170)
  - `core/gaspool.go` (L64-L90)
  - `miner/worker.go` (L455-L490)
  - `eth/protocols/eth/peer.go` (L625-L660)
  - `eth/gasestimator/gasestimator.go` (L50-L135)
  - `params/config.go` (L742-L744, L1287-L1310)
- Spec: `transactions.py` (L55-L90, L588-L812), `vm/gas.py` (L40-L165, L860-L885, L1038-L1086, L1160-L1228), `fork.py` (L500-L620, L1040-L1075)
- Nethermind:
  - `Nethermind.Evm/IntrinsicGasCalculator.cs`
  - `Nethermind.Evm/GasPolicy/EthereumGasPolicy.cs` (L721-L881)
  - `Nethermind.Specs/Forks/25_Amsterdam.cs`
  - `Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs` (L405-L415, L1040-L1075, L1664-L1670, L1790-L1802, L1938-L2010)
  - `Nethermind.Core/GasCostOf.cs`
  - `Nethermind.Core/Eip8038Constants.cs`
