Merge branch 'master' of https://github.com/0xjvn/go-ethereum into f/prune-txn-enable

This commit is contained in:
0xjvn 2026-03-17 18:45:49 +05:30
commit 3fcc799652
205 changed files with 5466 additions and 2131 deletions

1
.github/CODEOWNERS vendored
View file

@ -10,6 +10,7 @@ beacon/merkle/ @zsfelfoldi
beacon/types/ @zsfelfoldi @fjl beacon/types/ @zsfelfoldi @fjl
beacon/params/ @zsfelfoldi @fjl beacon/params/ @zsfelfoldi @fjl
cmd/evm/ @MariusVanDerWijden @lightclient cmd/evm/ @MariusVanDerWijden @lightclient
cmd/keeper/ @gballet
core/state/ @rjl493456442 core/state/ @rjl493456442
crypto/ @gballet @jwasinger @fjl crypto/ @gballet @jwasinger @fjl
core/ @rjl493456442 core/ @rjl493456442

View file

@ -69,8 +69,8 @@ jobs:
- name: Install cross toolchain - name: Install cross toolchain
run: | run: |
apt-get update sudo apt-get update
apt-get -yq --no-install-suggests --no-install-recommends install gcc-multilib sudo apt-get -yq --no-install-suggests --no-install-recommends install gcc-multilib
- name: Build - name: Build
run: go run build/ci.go test -arch 386 -short -p 8 run: go run build/ci.go test -arch 386 -short -p 8

102
AGENTS.md Normal file
View file

@ -0,0 +1,102 @@
# AGENTS
## Guidelines
- **Keep changes minimal and focused.** Only modify code directly related to the task at hand. Do not refactor unrelated code, rename existing variables or functions for style, or bundle unrelated fixes into the same commit or PR.
- **Do not add, remove, or update dependencies** unless the task explicitly requires it.
## Pre-Commit Checklist
Before every commit, run **all** of the following checks and ensure they pass:
### 1. Formatting
Before committing, always run `gofmt` and `goimports` on all modified files:
```sh
gofmt -w <modified files>
goimports -w <modified files>
```
### 2. Build All Commands
Verify that all tools compile successfully:
```sh
make all
```
This builds all executables under `cmd/`, including `keeper` which has special build requirements.
### 3. Tests
While iterating during development, use `-short` for faster feedback:
```sh
go run ./build/ci.go test -short
```
Before committing, run the full test suite **without** `-short` to ensure all tests pass, including the Ethereum execution-spec tests and all state/block test permutations:
```sh
go run ./build/ci.go test
```
### 4. Linting
```sh
go run ./build/ci.go lint
```
This runs additional style checks. Fix any issues before committing.
### 5. Generated Code
```sh
go run ./build/ci.go check_generate
```
Ensures that all generated files (e.g., `gen_*.go`) are up to date. If this fails, first install the required code generators by running `make devtools`, then run the appropriate `go generate` commands and include the updated files in your commit.
### 6. Dependency Hygiene
```sh
go run ./build/ci.go check_baddeps
```
Verifies that no forbidden dependencies have been introduced.
## What to include in commits
Do not commit binaries, whether they are produced by the main build or byproducts of investigations.
## Commit Message Format
Commit messages must be prefixed with the package(s) they modify, followed by a short lowercase description:
```
<package(s)>: description
```
Examples:
- `core/vm: fix stack overflow in PUSH instruction`
- `eth, rpc: make trace configs optional`
- `cmd/geth: add new flag for sync mode`
Use comma-separated package names when multiple areas are affected. Keep the description concise.
## Pull Request Title Format
PR titles follow the same convention as commit messages:
```
<list of modified paths>: description
```
Examples:
- `core/vm: fix stack overflow in PUSH instruction`
- `core, eth: add arena allocator support`
- `cmd/geth, internal/ethapi: refactor transaction args`
- `trie/archiver: streaming subtree archival to fix OOM`
Use the top-level package paths, comma-separated if multiple areas are affected. Only mention the directories with functional changes, interface changes that trickle all over the codebase should not generate an exhaustive list. The description should be a short, lowercase summary of the change.

View file

@ -4,7 +4,7 @@ ARG VERSION=""
ARG BUILDNUM="" ARG BUILDNUM=""
# Build Geth in a stock Go builder container # Build Geth in a stock Go builder container
FROM golang:1.24-alpine AS builder FROM golang:1.26-alpine AS builder
RUN apk add --no-cache gcc musl-dev linux-headers git RUN apk add --no-cache gcc musl-dev linux-headers git

View file

@ -4,7 +4,7 @@ ARG VERSION=""
ARG BUILDNUM="" ARG BUILDNUM=""
# Build Geth in a stock Go builder container # Build Geth in a stock Go builder container
FROM golang:1.24-alpine AS builder FROM golang:1.26-alpine AS builder
RUN apk add --no-cache gcc musl-dev linux-headers git RUN apk add --no-cache gcc musl-dev linux-headers git

View file

@ -87,6 +87,10 @@ func (ec *engineClient) updateLoop(headCh <-chan types.ChainHeadEvent) {
if status, err := ec.callForkchoiceUpdated(forkName, event); err == nil { if status, err := ec.callForkchoiceUpdated(forkName, event); err == nil {
log.Info("Successful ForkchoiceUpdated", "head", event.Block.Hash(), "status", status) log.Info("Successful ForkchoiceUpdated", "head", event.Block.Hash(), "status", status)
} else { } else {
if err.Error() == "beacon syncer reorging" {
log.Debug("Failed ForkchoiceUpdated", "head", event.Block.Hash(), "error", err)
continue // ignore beacon syncer reorging errors, this error can occur if the blsync is skipping a block
}
log.Error("Failed ForkchoiceUpdated", "head", event.Block.Hash(), "error", err) log.Error("Failed ForkchoiceUpdated", "head", event.Block.Hash(), "error", err)
} }
} }

View file

@ -21,6 +21,7 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
SlotNumber *hexutil.Uint64 `json:"slotNumber"`
} }
var enc PayloadAttributes var enc PayloadAttributes
enc.Timestamp = hexutil.Uint64(p.Timestamp) enc.Timestamp = hexutil.Uint64(p.Timestamp)
@ -28,6 +29,7 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
enc.Withdrawals = p.Withdrawals enc.Withdrawals = p.Withdrawals
enc.BeaconRoot = p.BeaconRoot enc.BeaconRoot = p.BeaconRoot
enc.SlotNumber = (*hexutil.Uint64)(p.SlotNumber)
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -39,6 +41,7 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
SlotNumber *hexutil.Uint64 `json:"slotNumber"`
} }
var dec PayloadAttributes var dec PayloadAttributes
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -62,5 +65,8 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
if dec.BeaconRoot != nil { if dec.BeaconRoot != nil {
p.BeaconRoot = dec.BeaconRoot p.BeaconRoot = dec.BeaconRoot
} }
if dec.SlotNumber != nil {
p.SlotNumber = (*uint64)(dec.SlotNumber)
}
return nil return nil
} }

View file

@ -34,6 +34,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
SlotNumber *hexutil.Uint64 `json:"slotNumber"`
} }
var enc ExecutableData var enc ExecutableData
enc.ParentHash = e.ParentHash enc.ParentHash = e.ParentHash
@ -58,6 +59,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
enc.Withdrawals = e.Withdrawals enc.Withdrawals = e.Withdrawals
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed) enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
enc.SlotNumber = (*hexutil.Uint64)(e.SlotNumber)
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -81,6 +83,7 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
SlotNumber *hexutil.Uint64 `json:"slotNumber"`
} }
var dec ExecutableData var dec ExecutableData
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -154,5 +157,8 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
if dec.ExcessBlobGas != nil { if dec.ExcessBlobGas != nil {
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
} }
if dec.SlotNumber != nil {
e.SlotNumber = (*uint64)(dec.SlotNumber)
}
return nil return nil
} }

View file

@ -50,6 +50,13 @@ var (
// ExecutionPayloadV3 has the syntax of ExecutionPayloadV2 and appends the new // ExecutionPayloadV3 has the syntax of ExecutionPayloadV2 and appends the new
// fields: blobGasUsed and excessBlobGas. // fields: blobGasUsed and excessBlobGas.
PayloadV3 PayloadVersion = 0x3 PayloadV3 PayloadVersion = 0x3
// PayloadV4 is the identifier of ExecutionPayloadV4 introduced in amsterdam fork.
//
// https://github.com/ethereum/execution-apis/blob/main/src/engine/amsterdam.md#executionpayloadv4
// ExecutionPayloadV4 has the syntax of ExecutionPayloadV3 and appends the new
// field slotNumber.
PayloadV4 PayloadVersion = 0x4
) )
//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go //go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go
@ -62,11 +69,13 @@ type PayloadAttributes struct {
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
SlotNumber *uint64 `json:"slotNumber"`
} }
// JSON type overrides for PayloadAttributes. // JSON type overrides for PayloadAttributes.
type payloadAttributesMarshaling struct { type payloadAttributesMarshaling struct {
Timestamp hexutil.Uint64 Timestamp hexutil.Uint64
SlotNumber *hexutil.Uint64
} }
//go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go //go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go
@ -90,6 +99,7 @@ type ExecutableData struct {
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *uint64 `json:"blobGasUsed"` BlobGasUsed *uint64 `json:"blobGasUsed"`
ExcessBlobGas *uint64 `json:"excessBlobGas"` ExcessBlobGas *uint64 `json:"excessBlobGas"`
SlotNumber *uint64 `json:"slotNumber"`
} }
// JSON type overrides for executableData. // JSON type overrides for executableData.
@ -104,6 +114,7 @@ type executableDataMarshaling struct {
Transactions []hexutil.Bytes Transactions []hexutil.Bytes
BlobGasUsed *hexutil.Uint64 BlobGasUsed *hexutil.Uint64
ExcessBlobGas *hexutil.Uint64 ExcessBlobGas *hexutil.Uint64
SlotNumber *hexutil.Uint64
} }
// StatelessPayloadStatusV1 is the result of a stateless payload execution. // StatelessPayloadStatusV1 is the result of a stateless payload execution.
@ -213,7 +224,7 @@ func encodeTransactions(txs []*types.Transaction) [][]byte {
return enc return enc
} }
func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { func DecodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
var txs = make([]*types.Transaction, len(enc)) var txs = make([]*types.Transaction, len(enc))
for i, encTx := range enc { for i, encTx := range enc {
var tx types.Transaction var tx types.Transaction
@ -251,7 +262,7 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
// for stateless execution, so it skips checking if the executable data hashes to // for stateless execution, so it skips checking if the executable data hashes to
// the requested hash (stateless has to *compute* the root hash, it's not given). // the requested hash (stateless has to *compute* the root hash, it's not given).
func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte) (*types.Block, error) { func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte) (*types.Block, error) {
txs, err := decodeTransactions(data.Transactions) txs, err := DecodeTransactions(data.Transactions)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -313,6 +324,7 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
BlobGasUsed: data.BlobGasUsed, BlobGasUsed: data.BlobGasUsed,
ParentBeaconRoot: beaconRoot, ParentBeaconRoot: beaconRoot,
RequestsHash: requestsHash, RequestsHash: requestsHash,
SlotNumber: data.SlotNumber,
} }
return types.NewBlockWithHeader(header). return types.NewBlockWithHeader(header).
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}), WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}),
@ -340,6 +352,7 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
Withdrawals: block.Withdrawals(), Withdrawals: block.Withdrawals(),
BlobGasUsed: block.BlobGasUsed(), BlobGasUsed: block.BlobGasUsed(),
ExcessBlobGas: block.ExcessBlobGas(), ExcessBlobGas: block.ExcessBlobGas(),
SlotNumber: block.SlotNumber(),
} }
// Add blobs. // Add blobs.

View file

@ -5,81 +5,102 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0 # https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
# version:golang 1.25.1 # version:golang 1.25.7
# https://go.dev/dl/ # https://go.dev/dl/
d010c109cee94d80efe681eab46bdea491ac906bf46583c32e9f0dbb0bd1a594 go1.25.1.src.tar.gz 178f2832820274b43e177d32f06a3ebb0129e427dd20a5e4c88df2c1763cf10a go1.25.7.src.tar.gz
1d622468f767a1b9fe1e1e67bd6ce6744d04e0c68712adc689748bbeccb126bb go1.25.1.darwin-amd64.tar.gz 81bf2a1f20633f62d55d826d82dde3b0570cf1408a91e15781b266037299285b go1.25.7.aix-ppc64.tar.gz
68deebb214f39d542e518ebb0598a406ab1b5a22bba8ec9ade9f55fb4dd94a6c go1.25.1.darwin-arm64.tar.gz bf5050a2152f4053837b886e8d9640c829dbacbc3370f913351eb0904cb706f5 go1.25.7.darwin-amd64.tar.gz
d03cdcbc9bd8baf5cf028de390478e9e2b3e4d0afe5a6582dedc19bfe6a263b2 go1.25.1.linux-386.tar.gz ff18369ffad05c57d5bed888b660b31385f3c913670a83ef557cdfd98ea9ae1b go1.25.7.darwin-arm64.tar.gz
7716a0d940a0f6ae8e1f3b3f4f36299dc53e31b16840dbd171254312c41ca12e go1.25.1.linux-amd64.tar.gz c5dccd7f192dd7b305dc209fb316ac1917776d74bd8e4d532ef2772f305bf42a go1.25.7.dragonfly-amd64.tar.gz
65a3e34fb2126f55b34e1edfc709121660e1be2dee6bdf405fc399a63a95a87d go1.25.1.linux-arm64.tar.gz a2de97c8ac74bf64b0ae73fe9d379e61af530e061bc7f8f825044172ffe61a8b go1.25.7.freebsd-386.tar.gz
eb949be683e82a99e9861dafd7057e31ea40b161eae6c4cd18fdc0e8c4ae6225 go1.25.1.linux-armv6l.tar.gz 055f9e138787dcafa81eb0314c8ff70c6dd0f6dba1e8a6957fef5d5efd1ab8fd go1.25.7.freebsd-amd64.tar.gz
be13d5479b8c75438f2efcaa8c191fba3af684b3228abc9c99c7aa8502f34424 go1.25.1.windows-386.zip 60e7f7a7c990f0b9539ac8ed668155746997d404643a4eecd47b3dee1b7e710b go1.25.7.freebsd-arm.tar.gz
4a974de310e7ee1d523d2fcedb114ba5fa75408c98eb3652023e55ccf3fa7cab go1.25.1.windows-amd64.zip 631e03d5fd4c526e2f499154d8c6bf4cb081afb2fff171c428722afc9539d53a go1.25.7.freebsd-arm64.tar.gz
45ab4290adbd6ee9e7f18f0d57eaa9008fdbef590882778ed93eac3c8cca06c5 go1.25.1.aix-ppc64.tar.gz 8a264fd685823808140672812e3ad9c43f6ad59444c0dc14cdd3a1351839ddd5 go1.25.7.freebsd-riscv64.tar.gz
2e3c1549bed3124763774d648f291ac42611232f48320ebbd23517c909c09b81 go1.25.1.dragonfly-amd64.tar.gz 57c672447d906a1bcab98f2b11492d54521a791aacbb4994a25169e59cbe289a go1.25.7.illumos-amd64.tar.gz
dc0198dd4ec520e13f26798def8750544edf6448d8e9c43fd2a814e4885932af go1.25.1.freebsd-386.tar.gz 2866517e9ca81e6a2e85a930e9b11bc8a05cfeb2fc6dc6cb2765e7fb3c14b715 go1.25.7.linux-386.tar.gz
c4f1a7e7b258406e6f3b677ecdbd97bbb23ff9c0d44be4eb238a07d360f69ac8 go1.25.1.freebsd-amd64.tar.gz 12e6d6a191091ae27dc31f6efc630e3a3b8ba409baf3573d955b196fdf086005 go1.25.7.linux-amd64.tar.gz
7772fc5ff71ed39297ec0c1599fc54e399642c9b848eac989601040923b0de9c go1.25.1.freebsd-arm.tar.gz ba611a53534135a81067240eff9508cd7e256c560edd5d8c2fef54f083c07129 go1.25.7.linux-arm64.tar.gz
5bb011d5d5b6218b12189f07aa0be618ab2002662fff1ca40afba7389735c207 go1.25.1.freebsd-arm64.tar.gz 1ba07e0eb86b839e72467f4b5c7a5597d07f30bcf5563c951410454f7cda5266 go1.25.7.linux-armv6l.tar.gz
ccac716240cb049bebfafcb7eebc3758512178a4c51fc26da9cc032035d850c8 go1.25.1.freebsd-riscv64.tar.gz 775753fc5952a334c415f08768df2f0b73a3228a16e8f5f63d545daacb4e3357 go1.25.7.linux-loong64.tar.gz
cc53910ffb9fcfdd988a9fa25b5423bae1cfa01b19616be646700e1f5453b466 go1.25.1.illumos-amd64.tar.gz 1a023bb367c5fbb4c637a2f6dc23ff17c6591ad929ce16ea88c74d857153b307 go1.25.7.linux-mips.tar.gz
efe809f923bcedab44bf7be2b3af8d182b512b1bf9c07d302e0c45d26c8f56f3 go1.25.1.linux-loong64.tar.gz a8e97223d8aa6fdfd45f132a4784d2f536bbac5f3d63a24b63d33b6bfe1549af go1.25.7.linux-mips64.tar.gz
c0de33679f6ed68991dc42dc4a602e74a666e3e166c1748ee1b5d1a7ea2ffbb2 go1.25.1.linux-mips.tar.gz eb9edb6223330d5e20275667c65dea076b064c08e595fe4eba5d7d6055cfaccf go1.25.7.linux-mips64le.tar.gz
c270f7b0c0bdfbcd54fef4481227c40d41bb518f9ae38ee930870f04a0a6a589 go1.25.1.linux-mips64.tar.gz 9c1e693552a5f9bb9e0012d1c5e01456ecefbc59bef53a77305222ce10aba368 go1.25.7.linux-mipsle.tar.gz
80be871ba9c944f34d1868cdf5047e1cf2e1289fe08cdb90e2453d2f0d6965ae go1.25.1.linux-mips64le.tar.gz 28a788798e7329acbbc0ac2caa5e4368b1e5ede646cc24429c991214cfb45c63 go1.25.7.linux-ppc64.tar.gz
9f09defa9bb22ebf2cde76162f40958564e57ce5c2b3649bc063bebcbc9294c1 go1.25.1.linux-mipsle.tar.gz 42124c0edc92464e2b37b2d7fcd3658f0c47ebd6a098732415a522be8cb88e3f go1.25.7.linux-ppc64le.tar.gz
2c76b7d278c1d43ad19d478ad3f0f05e7b782b64b90870701b314fa48b5f43c6 go1.25.1.linux-ppc64.tar.gz 88d59c6893c8425875d6eef8e3434bc2fa2552e5ad4c058c6cd8cd710a0301c8 go1.25.7.linux-riscv64.tar.gz
8b0c8d3ee5b1b5c28b6bd63dc4438792012e01d03b4bf7a61d985c87edab7d1f go1.25.1.linux-ppc64le.tar.gz c6b77facf666dc68195ecab05dbf0ebb4e755b2a8b7734c759880557f1c29b0c go1.25.7.linux-s390x.tar.gz
22fe934a9d0c9c57275716c55b92d46ebd887cec3177c9140705efa9f84ba1e2 go1.25.1.linux-riscv64.tar.gz f14c184d9ade0ee04c7735d4071257b90896ecbde1b32adae84135f055e6399b go1.25.7.netbsd-386.tar.gz
9cfe517ba423f59f3738ca5c3d907c103253cffbbcc2987142f79c5de8c1bf93 go1.25.1.linux-s390x.tar.gz 7e7389e404dca1088c31f0fc07f1dd60891d7182bcd621469c14f7e79eceb3ff go1.25.7.netbsd-amd64.tar.gz
6af8a08353e76205d5b743dd7a3f0126684f96f62be0a31b75daf9837e512c46 go1.25.1.netbsd-386.tar.gz 70388bb3ef2f03dbf1357e9056bd09034a67e018262557354f8cf549766b3f9d go1.25.7.netbsd-arm.tar.gz
e5d534ff362edb1bd8c8e10892b6a027c4c1482454245d1529167676498684c7 go1.25.1.netbsd-amd64.tar.gz 8c1cda9d25bfc9b18d24d5f95fc23949dd3ff99fa408a6cfa40e2cf12b07e362 go1.25.7.netbsd-arm64.tar.gz
88bcf39254fdcea6a199c1c27d787831b652427ce60851ae9e41a3d7eb477f45 go1.25.1.netbsd-arm.tar.gz 42f0d1bfbe39b8401cccb84dd66b30795b97bfc9620dfdc17c5cd4fcf6495cb0 go1.25.7.openbsd-386.tar.gz
d7c2eabe1d04ee47bcaea2816fdd90dbd25d90d4dfa756faa9786c788e4f3a4e go1.25.1.netbsd-arm64.tar.gz e514879c0a28bc32123cd52c4c093de912477fe83f36a6d07517d066ef55391a go1.25.7.openbsd-amd64.tar.gz
14a2845977eb4dde11d929858c437a043467c427db87899935e90cee04a38d72 go1.25.1.openbsd-386.tar.gz 8cd22530695a0218232bf7efea8f162df1697a3106942ac4129b8c3de39ce4ef go1.25.7.openbsd-arm.tar.gz
d27ac54b38a13a09c81e67c82ac70d387037341c85c3399291c73e13e83fdd8c go1.25.1.openbsd-amd64.tar.gz 938720f6ebc0d1c53d7840321d3a31f29fd02496e84a6538f442a9311dc1cc9a go1.25.7.openbsd-arm64.tar.gz
0f4ab5f02500afa4befd51fed1e8b45e4d07ca050f641cc3acc76eaa4027b2c3 go1.25.1.openbsd-arm.tar.gz a4c378b73b98f89a3596c2ef51aabbb28783d9ca29f7e317d8ca07939660ce6f go1.25.7.openbsd-ppc64.tar.gz
d46c3bd156843656f7f3cb0dec27ea51cd926ec3f7b80744bf8156e67c1c812f go1.25.1.openbsd-arm64.tar.gz 937b58734fbeaa8c7941a0e4285e7e84b7885396e8d11c23f9ab1a8ff10ff20e go1.25.7.openbsd-riscv64.tar.gz
c550514c67f22e409be10e40eace761e2e43069f4ef086ae6e60aac736c2b679 go1.25.1.openbsd-ppc64.tar.gz 61a093c8c5244916f25740316386bb9f141545dcf01b06a79d1c78ece488403e go1.25.7.plan9-386.tar.gz
8a09a8714a2556eb13fc1f10b7ce2553fcea4971e3330fc3be0efd24aab45734 go1.25.1.openbsd-riscv64.tar.gz 7fc8f6689c9de8ccb7689d2278035fa83c2d601409101840df6ddfe09ba58699 go1.25.7.plan9-amd64.tar.gz
b0e1fefaf0c7abd71f139a54eee9767944aff5f0bc9d69c968234804884e552f go1.25.1.plan9-386.tar.gz 9661dff8eaeeb62f1c3aadbc5ff189a2e6744e1ec885e32dbcb438f58a34def5 go1.25.7.plan9-arm.tar.gz
e94732c94f149690aa0ab11c26090577211b4a988137cb2c03ec0b54e750402e go1.25.1.plan9-amd64.tar.gz 28ecba0e1d7950c8b29a4a04962dd49c3bf5221f55a44f17d98f369f82859cf4 go1.25.7.solaris-amd64.tar.gz
7eb80e9de1e817d9089a54e8c7c5c8d8ed9e5fb4d4a012fc0f18fc422a484f0c go1.25.1.plan9-arm.tar.gz baa6b488291801642fa620026169e38bec2da2ac187cd3ae2145721cf826bbc3 go1.25.7.windows-386.zip
1261dfad7c4953c0ab90381bc1242dc54e394db7485c59349428d532b2273343 go1.25.1.solaris-amd64.tar.gz c75e5f4ff62d085cc0017be3ad19d5536f46825fa05db06ec468941f847e3228 go1.25.7.windows-amd64.zip
04bc3c078e9e904c4d58d6ac2532a5bdd402bd36a9ff0b5949b3c5e6006a05ee go1.25.1.windows-arm64.zip 807033f85931bc4a589ca8497535dcbeb1f30d506e47fa200f5f04c4a71c3d9f go1.25.7.windows-arm64.zip
# version:golangci 2.4.0 # version:golangci 2.10.1
# https://github.com/golangci/golangci-lint/releases/ # https://github.com/golangci/golangci-lint/releases/
# https://github.com/golangci/golangci-lint/releases/download/v2.4.0/ # https://github.com/golangci/golangci-lint/releases/download/v2.10.1
7904ce63f79db44934939cf7a063086ea0ea98e9b19eba0a9d52ccdd0d21951c golangci-lint-2.4.0-darwin-amd64.tar.gz 66fb0da81b8033b477f97eea420d4b46b230ca172b8bb87c6610109f3772b6b6 golangci-lint-2.10.1-darwin-amd64.tar.gz
cd4dd53fa09b6646baff5fd22b8c64d91db02c21c7496df27992d75d34feec59 golangci-lint-2.4.0-darwin-arm64.tar.gz 03bfadf67e52b441b7ec21305e501c717df93c959836d66c7f97312654acb297 golangci-lint-2.10.1-darwin-arm64.tar.gz
d58f426ebe14cc257e81562b4bf37a488ffb4ffbbb3ec73041eb3b38bb25c0e1 golangci-lint-2.4.0-freebsd-386.tar.gz c9a44658ccc8f7b8dbbd4ae6020ba91c1a5d3987f4d91ced0f7d2bea013e57ca golangci-lint-2.10.1-freebsd-386.tar.gz
6ec4a6177fc6c0dd541fbcb3a7612845266d020d35cc6fa92959220cdf64ca39 golangci-lint-2.4.0-freebsd-amd64.tar.gz a513c5cb4e0f5bd5767001af9d5e97e7868cfc2d9c46739a4df93e713cfb24af golangci-lint-2.10.1-freebsd-amd64.tar.gz
4d473e3e71c01feaa915a0604fb35758b41284fb976cdeac3f842118d9ee7e17 golangci-lint-2.4.0-freebsd-armv6.tar.gz 2ef38eefc4b5cee2febacb75a30579526e5656c16338a921d80e59a8e87d4425 golangci-lint-2.10.1-freebsd-arm64.tar.gz
58727746c6530801a3f9a702a5945556a5eb7e88809222536dd9f9d54cafaeff golangci-lint-2.4.0-freebsd-armv7.tar.gz 8fea6766318b4829e766bbe325f10191d75297dcc44ae35bf374816037878e38 golangci-lint-2.10.1-freebsd-armv6.tar.gz
fbf28c662760e24c32f82f8d16dffdb4a82de7726a52ba1fad94f890c22997ea golangci-lint-2.4.0-illumos-amd64.tar.gz 30b629870574d6254f3e8804e5a74b34f98e1263c9d55465830d739c88b862ed golangci-lint-2.10.1-freebsd-armv7.tar.gz
a15a000a8981ef665e971e0f67e2acda9066a9e37a59344393b7351d8fb49c81 golangci-lint-2.4.0-linux-386.tar.gz c0db839f866ce80b1b6c96167aa101cfe50d9c936f42d942a3c1cbdc1801af68 golangci-lint-2.10.1-illumos-amd64.tar.gz
fae792524c04424c0ac369f5b8076f04b45cf29fc945a370e55d369a8dc11840 golangci-lint-2.4.0-linux-amd64.tar.gz 280eb56636e9175f671cd7b755d7d67f628ae2ed00a164d1e443c43c112034e5 golangci-lint-2.10.1-linux-386.deb
70ac11f55b80ec78fd3a879249cc9255121b8dfd7f7ed4fc46ed137f4abf17e7 golangci-lint-2.4.0-linux-arm64.tar.gz 065a7d99da61dc7dfbfef2e2d7053dd3fa6672598f2747117aa4bb5f45e7df7f golangci-lint-2.10.1-linux-386.rpm
4acdc40e5cebe99e4e7ced358a05b2e71789f409b41cb4f39bbb86ccfa14b1dc golangci-lint-2.4.0-linux-armv6.tar.gz a55918c03bb413b2662287653ab2ae2fef4e37428b247dad6348724adde9d770 golangci-lint-2.10.1-linux-386.tar.gz
2a68749568fa22b4a97cb88dbea655595563c795076536aa6c087f7968784bf3 golangci-lint-2.4.0-linux-armv7.tar.gz 8aa9b3aa14f39745eeb7fc7ff50bcac683e785397d1e4bc9afd2184b12c4ce86 golangci-lint-2.10.1-linux-amd64.deb
9e3369afb023711036dcb0b4f45c9fe2792af962fa1df050c9f6ac101a6c5d73 golangci-lint-2.4.0-linux-loong64.tar.gz 62a111688e9e305032334a2cbc84f4d971b64bb3bffc99d3f80081d57fb25e32 golangci-lint-2.10.1-linux-amd64.rpm
bb9143d6329be2c4dbfffef9564078e7da7d88e7dde6c829b6263d98e072229e golangci-lint-2.4.0-linux-mips64.tar.gz dfa775874cf0561b404a02a8f4481fc69b28091da95aa697259820d429b09c99 golangci-lint-2.10.1-linux-amd64.tar.gz
5ad1765b40d56cd04d4afd805b3ba6f4bfd9b36181da93c31e9b17e483d8608d golangci-lint-2.4.0-linux-mips64le.tar.gz b3f36937e8ea1660739dc0f5c892ea59c9c21ed4e75a91a25957c561f7f79a55 golangci-lint-2.10.1-linux-arm64.deb
918936fb9c0d5ba96bef03cf4348b03938634cfcced49be1e9bb29cb5094fa73 golangci-lint-2.4.0-linux-ppc64le.tar.gz 36d50314d53683b1f1a2a6cedfb5a9468451b481c64ab9e97a8e843ea088074d golangci-lint-2.10.1-linux-arm64.rpm
f7474c638e1fb67ebbdc654b55ca0125377ea0bc88e8fee8d964a4f24eacf828 golangci-lint-2.4.0-linux-riscv64.tar.gz 6652b42ae02915eb2f9cb2a2e0cac99514c8eded8388d88ae3e06e1a52c00de8 golangci-lint-2.10.1-linux-arm64.tar.gz
b617a9543997c8bfceaffa88a75d4e595030c6add69fba800c1e4d8f5fe253dd golangci-lint-2.4.0-linux-s390x.tar.gz a32d8d318e803496812dd3461f250e52ccc7f53c47b95ce404a9cf55778ceb6a golangci-lint-2.10.1-linux-armv6.deb
7db027b03a9ba328f795215b04f594036837bc7dd0dd7cd16776b02a6167981c golangci-lint-2.4.0-netbsd-386.tar.gz 41d065f4c8ea165a1531abea644988ee2e973e4f0b49f9725ed3b979dac45112 golangci-lint-2.10.1-linux-armv6.rpm
52d8f9393f4313df0a62b752c37775e3af0b818e43e8dd28954351542d7c60bc golangci-lint-2.4.0-netbsd-amd64.tar.gz 59159a4df03aabbde69d15c7b7b3df143363cbb41f4bd4b200caffb8e34fb734 golangci-lint-2.10.1-linux-armv6.tar.gz
5c0086027fb5a4af3829e530c8115db4b35d11afe1914322eef528eb8cd38c69 golangci-lint-2.4.0-netbsd-arm64.tar.gz b2e8ec0e050a1e2251dfe1561434999d202f5a3f9fa47ce94378b0fd1662ea5a golangci-lint-2.10.1-linux-armv7.deb
6b779d6ed1aed87cefe195cc11759902b97a76551b593312c6833f2635a3488f golangci-lint-2.4.0-netbsd-armv6.tar.gz 28c9331429a497da27e9c77846063bd0e8275e878ffedb4eb9e9f21d24771cc0 golangci-lint-2.10.1-linux-armv7.rpm
f00d1f4b7ec3468a0f9fffd0d9ea036248b029b7621cbc9a59c449ef94356d09 golangci-lint-2.4.0-netbsd-armv7.tar.gz 818f33e95b273e3769284b25563b51ef6a294e9e25acf140fda5830c075a1a59 golangci-lint-2.10.1-linux-armv7.tar.gz
3ce671b0b42b58e35066493aab75a7e2826c9e079988f1ba5d814a4029faaf87 golangci-lint-2.4.0-windows-386.zip 6b6b85ed4b7c27f51097dd681523000409dde835e86e6e314e87be4bb013e2ab golangci-lint-2.10.1-linux-loong64.deb
003112f7a56746feaabf20b744054bf9acdf900c9e77176383623c4b1d76aaa9 golangci-lint-2.4.0-windows-amd64.zip 94050a0cf06169e2ae44afb307dcaafa7d7c3b38c0c23b5652cf9cb60f0c337f golangci-lint-2.10.1-linux-loong64.rpm
dc0c2092af5d47fc2cd31a1dfe7b4c7e765fab22de98bd21ef2ffcc53ad9f54f golangci-lint-2.4.0-windows-arm64.zip 25820300fccb8c961c1cdcb1f77928040c079e04c43a3a5ceb34b1cb4a1c5c8d golangci-lint-2.10.1-linux-loong64.tar.gz
0263d23e20a260cb1592d35e12a388f99efe2c51b3611fdc66fbd9db1fce664d golangci-lint-2.4.0-windows-armv6.zip 98bf39d10139fdcaa37f94950e9bbb8888660ae468847ae0bf1cb5bf67c1f68b golangci-lint-2.10.1-linux-mips64.deb
9403c03bf648e6313036e0273149d44bad1b9ad53889b6d00e4ccb842ba3c058 golangci-lint-2.4.0-windows-armv7.zip df3ce5f03808dcceaa8b683d1d06e95c885f09b59dc8e15deb840fbe2b3e3299 golangci-lint-2.10.1-linux-mips64.rpm
972508dda523067e6e6a1c8e6609d63bc7c4153819c11b947d439235cf17bac2 golangci-lint-2.10.1-linux-mips64.tar.gz
1d37f2919e183b5bf8b1777ed8c4b163d3b491d0158355a7999d647655cbbeb6 golangci-lint-2.10.1-linux-mips64le.deb
e341d031002cd09a416329ed40f674231051a38544b8f94deb2d1708ce1f4a6f golangci-lint-2.10.1-linux-mips64le.rpm
393560122b9cb5538df0c357d30eb27b6ee563533fbb9b138c8db4fd264002af golangci-lint-2.10.1-linux-mips64le.tar.gz
21ca46b6a96442e8957677a3ca059c6b93674a68a01b1c71f4e5df0ea2e96d19 golangci-lint-2.10.1-linux-ppc64le.deb
57fe0cbca0a9bbdf1547c5e8aa7d278e6896b438d72a541bae6bc62c38b43d1e golangci-lint-2.10.1-linux-ppc64le.rpm
e2883db9fa51584e5e203c64456f29993550a7faadc84e3faccdb48f0669992e golangci-lint-2.10.1-linux-ppc64le.tar.gz
aa6da0e98ab0ba3bb7582e112174c349907d5edfeff90a551dca3c6eecf92fc0 golangci-lint-2.10.1-linux-riscv64.deb
3c68d76cd884a7aad206223a980b9c20bb9ea74b560fa27ed02baf2389189234 golangci-lint-2.10.1-linux-riscv64.rpm
3bca11bfac4197205639cbd4676a5415054e629ac6c12ea10fcbe33ef852d9c3 golangci-lint-2.10.1-linux-riscv64.tar.gz
0c6aed2ce49db2586adbac72c80d871f06feb1caf4c0763a5ca98fec809a8f0b golangci-lint-2.10.1-linux-s390x.deb
16c285adfe1061d69dd8e503be69f87c7202857c6f4add74ac02e3571158fbec golangci-lint-2.10.1-linux-s390x.rpm
21011ad368eb04f024201b832095c6b5f96d0888de194cca5bfe4d9307d6364b golangci-lint-2.10.1-linux-s390x.tar.gz
7b5191e77a70485918712e31ed55159956323e4911bab1b67569c9d86e1b75eb golangci-lint-2.10.1-netbsd-386.tar.gz
07801fd38d293ebad10826f8285525a39ea91ce5ddad77d05bfa90bda9c884a9 golangci-lint-2.10.1-netbsd-amd64.tar.gz
7e7219d71c1bf33b98c328c93dc0560706dd896a1c43c44696e5222fc9d7446e golangci-lint-2.10.1-netbsd-arm64.tar.gz
92fbc90b9eec0e572269b0f5492a2895c426b086a68372fde49b7e4d4020863e golangci-lint-2.10.1-netbsd-armv6.tar.gz
f67b3ae1f47caeefa507a4ebb0c8336958a19011fe48766443212030f75d004b golangci-lint-2.10.1-netbsd-armv7.tar.gz
a40bc091c10cea84eaee1a90b84b65f5e8652113b0a600bb099e4e4d9d7caddb golangci-lint-2.10.1-windows-386.zip
c60c87695e79db8e320f0e5be885059859de52bb5ee5f11be5577828570bc2a3 golangci-lint-2.10.1-windows-amd64.zip
636ab790c8dcea8034aa34aba6031ca3893d68f7eda000460ab534341fadbab1 golangci-lint-2.10.1-windows-arm64.zip
# This is the builder on PPA that will build Go itself (inception-y), don't modify! # This is the builder on PPA that will build Go itself (inception-y), don't modify!
# #

View file

@ -168,8 +168,6 @@ var (
"focal", // 20.04, EOL: 04/2030 "focal", // 20.04, EOL: 04/2030
"jammy", // 22.04, EOL: 04/2032 "jammy", // 22.04, EOL: 04/2032
"noble", // 24.04, EOL: 04/2034 "noble", // 24.04, EOL: 04/2034
"oracular", // 24.10, EOL: 07/2025
"plucky", // 25.04, EOL: 01/2026
} }
// This is where the tests should be unpacked. // This is where the tests should be unpacked.
@ -1198,7 +1196,7 @@ func doWindowsInstaller(cmdline []string) {
var ( var (
arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging") arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging")
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`) signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`)
signify = flag.String("signify key", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`) signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`)
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
) )

View file

@ -155,7 +155,7 @@ func (c *Conn) ReadEth() (any, error) {
var msg any var msg any
switch int(code) { switch int(code) {
case eth.StatusMsg: case eth.StatusMsg:
msg = new(eth.StatusPacket69) msg = new(eth.StatusPacket)
case eth.GetBlockHeadersMsg: case eth.GetBlockHeadersMsg:
msg = new(eth.GetBlockHeadersPacket) msg = new(eth.GetBlockHeadersPacket)
case eth.BlockHeadersMsg: case eth.BlockHeadersMsg:
@ -164,10 +164,6 @@ func (c *Conn) ReadEth() (any, error) {
msg = new(eth.GetBlockBodiesPacket) msg = new(eth.GetBlockBodiesPacket)
case eth.BlockBodiesMsg: case eth.BlockBodiesMsg:
msg = new(eth.BlockBodiesPacket) msg = new(eth.BlockBodiesPacket)
case eth.NewBlockMsg:
msg = new(eth.NewBlockPacket)
case eth.NewBlockHashesMsg:
msg = new(eth.NewBlockHashesPacket)
case eth.TransactionsMsg: case eth.TransactionsMsg:
msg = new(eth.TransactionsPacket) msg = new(eth.TransactionsPacket)
case eth.NewPooledTransactionHashesMsg: case eth.NewPooledTransactionHashesMsg:
@ -229,7 +225,7 @@ func (c *Conn) ReadSnap() (any, error) {
} }
// dialAndPeer creates a peer connection and runs the handshake. // dialAndPeer creates a peer connection and runs the handshake.
func (s *Suite) dialAndPeer(status *eth.StatusPacket69) (*Conn, error) { func (s *Suite) dialAndPeer(status *eth.StatusPacket) (*Conn, error) {
c, err := s.dial() c, err := s.dial()
if err != nil { if err != nil {
return nil, err return nil, err
@ -242,7 +238,7 @@ func (s *Suite) dialAndPeer(status *eth.StatusPacket69) (*Conn, error) {
// peer performs both the protocol handshake and the status message // peer performs both the protocol handshake and the status message
// exchange with the node in order to peer with it. // exchange with the node in order to peer with it.
func (c *Conn) peer(chain *Chain, status *eth.StatusPacket69) error { func (c *Conn) peer(chain *Chain, status *eth.StatusPacket) error {
if err := c.handshake(); err != nil { if err := c.handshake(); err != nil {
return fmt.Errorf("handshake failed: %v", err) return fmt.Errorf("handshake failed: %v", err)
} }
@ -315,7 +311,7 @@ func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) {
} }
// statusExchange performs a `Status` message exchange with the given node. // statusExchange performs a `Status` message exchange with the given node.
func (c *Conn) statusExchange(chain *Chain, status *eth.StatusPacket69) error { func (c *Conn) statusExchange(chain *Chain, status *eth.StatusPacket) error {
loop: loop:
for { for {
code, data, err := c.Read() code, data, err := c.Read()
@ -324,7 +320,7 @@ loop:
} }
switch code { switch code {
case eth.StatusMsg + protoOffset(ethProto): case eth.StatusMsg + protoOffset(ethProto):
msg := new(eth.StatusPacket69) msg := new(eth.StatusPacket)
if err := rlp.DecodeBytes(data, &msg); err != nil { if err := rlp.DecodeBytes(data, &msg); err != nil {
return fmt.Errorf("error decoding status packet: %w", err) return fmt.Errorf("error decoding status packet: %w", err)
} }
@ -363,7 +359,7 @@ loop:
} }
if status == nil { if status == nil {
// default status message // default status message
status = &eth.StatusPacket69{ status = &eth.StatusPacket{
ProtocolVersion: uint32(c.negotiatedProtoVersion), ProtocolVersion: uint32(c.negotiatedProtoVersion),
NetworkID: chain.config.ChainID.Uint64(), NetworkID: chain.config.ChainID.Uint64(),
Genesis: chain.blocks[0].Hash(), Genesis: chain.blocks[0].Hash(),

View file

@ -447,7 +447,7 @@ func (s *Suite) TestGetReceipts(t *utesting.T) {
t.Fatalf("could not write to connection: %v", err) t.Fatalf("could not write to connection: %v", err)
} }
// Wait for response. // Wait for response.
resp := new(eth.ReceiptsPacket[*eth.ReceiptList69]) resp := new(eth.ReceiptsPacket)
if err := conn.ReadMsg(ethProto, eth.ReceiptsMsg, &resp); err != nil { if err := conn.ReadMsg(ethProto, eth.ReceiptsMsg, &resp); err != nil {
t.Fatalf("error reading block bodies msg: %v", err) t.Fatalf("error reading block bodies msg: %v", err)
} }

View file

@ -52,7 +52,7 @@ func (s *Suite) AllTests() []utesting.Test {
{Name: "Ping", Fn: s.TestPing}, {Name: "Ping", Fn: s.TestPing},
{Name: "PingLargeRequestID", Fn: s.TestPingLargeRequestID}, {Name: "PingLargeRequestID", Fn: s.TestPingLargeRequestID},
{Name: "PingMultiIP", Fn: s.TestPingMultiIP}, {Name: "PingMultiIP", Fn: s.TestPingMultiIP},
{Name: "PingHandshakeInterrupted", Fn: s.TestPingHandshakeInterrupted}, {Name: "HandshakeResend", Fn: s.TestHandshakeResend},
{Name: "TalkRequest", Fn: s.TestTalkRequest}, {Name: "TalkRequest", Fn: s.TestTalkRequest},
{Name: "FindnodeZeroDistance", Fn: s.TestFindnodeZeroDistance}, {Name: "FindnodeZeroDistance", Fn: s.TestFindnodeZeroDistance},
{Name: "FindnodeResults", Fn: s.TestFindnodeResults}, {Name: "FindnodeResults", Fn: s.TestFindnodeResults},
@ -158,22 +158,20 @@ the attempt from a different IP.`)
} }
} }
// TestPingHandshakeInterrupted starts a handshake, but doesn't finish it and sends a second ordinary message // TestHandshakeResend starts a handshake, but doesn't finish it and sends a second ordinary message
// packet instead of a handshake message packet. The remote node should respond with // packet instead of a handshake message packet. The remote node should repeat the previous WHOAREYOU
// another WHOAREYOU challenge for the second packet. // challenge for the first PING.
func (s *Suite) TestPingHandshakeInterrupted(t *utesting.T) { func (s *Suite) TestHandshakeResend(t *utesting.T) {
t.Log(`TestPingHandshakeInterrupted starts a handshake, but doesn't finish it and sends a second ordinary message
packet instead of a handshake message packet. The remote node should respond with
another WHOAREYOU challenge for the second packet.`)
conn, l1 := s.listen1(t) conn, l1 := s.listen1(t)
defer conn.close() defer conn.close()
// First PING triggers challenge. // First PING triggers challenge.
ping := &v5wire.Ping{ReqID: conn.nextReqID()} ping := &v5wire.Ping{ReqID: conn.nextReqID()}
conn.write(l1, ping, nil) conn.write(l1, ping, nil)
var challenge1 *v5wire.Whoareyou
switch resp := conn.read(l1).(type) { switch resp := conn.read(l1).(type) {
case *v5wire.Whoareyou: case *v5wire.Whoareyou:
challenge1 = resp
t.Logf("got WHOAREYOU for PING") t.Logf("got WHOAREYOU for PING")
default: default:
t.Fatal("expected WHOAREYOU, got", resp) t.Fatal("expected WHOAREYOU, got", resp)
@ -181,9 +179,16 @@ another WHOAREYOU challenge for the second packet.`)
// Send second PING. // Send second PING.
ping2 := &v5wire.Ping{ReqID: conn.nextReqID()} ping2 := &v5wire.Ping{ReqID: conn.nextReqID()}
switch resp := conn.reqresp(l1, ping2).(type) { conn.write(l1, ping2, nil)
case *v5wire.Pong: switch resp := conn.read(l1).(type) {
checkPong(t, resp, ping2, l1) case *v5wire.Whoareyou:
if resp.Nonce != challenge1.Nonce {
t.Fatalf("wrong nonce %x in WHOAREYOU (want %x)", resp.Nonce[:], challenge1.Nonce[:])
}
if !bytes.Equal(resp.ChallengeData, challenge1.ChallengeData) {
t.Fatalf("wrong ChallengeData in resent WHOAREYOU (want %x)", resp.ChallengeData, challenge1.ChallengeData)
}
resp.Node = conn.remote
default: default:
t.Fatal("expected WHOAREYOU, got", resp) t.Fatal("expected WHOAREYOU, got", resp)
} }

View file

@ -56,6 +56,7 @@ type header struct {
BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
SlotNumber *uint64 `json:"slotNumber" rlp:"optional"`
} }
type headerMarshaling struct { type headerMarshaling struct {
@ -68,6 +69,7 @@ type headerMarshaling struct {
BaseFee *math.HexOrDecimal256 BaseFee *math.HexOrDecimal256
BlobGasUsed *math.HexOrDecimal64 BlobGasUsed *math.HexOrDecimal64
ExcessBlobGas *math.HexOrDecimal64 ExcessBlobGas *math.HexOrDecimal64
SlotNumber *math.HexOrDecimal64
} }
type bbInput struct { type bbInput struct {
@ -136,6 +138,7 @@ func (i *bbInput) ToBlock() *types.Block {
BlobGasUsed: i.Header.BlobGasUsed, BlobGasUsed: i.Header.BlobGasUsed,
ExcessBlobGas: i.Header.ExcessBlobGas, ExcessBlobGas: i.Header.ExcessBlobGas,
ParentBeaconRoot: i.Header.ParentBeaconBlockRoot, ParentBeaconRoot: i.Header.ParentBeaconBlockRoot,
SlotNumber: i.Header.SlotNumber,
} }
// Fill optional values. // Fill optional values.

View file

@ -102,6 +102,7 @@ type stEnv struct {
ParentExcessBlobGas *uint64 `json:"parentExcessBlobGas,omitempty"` ParentExcessBlobGas *uint64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *uint64 `json:"parentBlobGasUsed,omitempty"` ParentBlobGasUsed *uint64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
SlotNumber *uint64 `json:"slotNumber"`
} }
type stEnvMarshaling struct { type stEnvMarshaling struct {
@ -120,6 +121,7 @@ type stEnvMarshaling struct {
ExcessBlobGas *math.HexOrDecimal64 ExcessBlobGas *math.HexOrDecimal64
ParentExcessBlobGas *math.HexOrDecimal64 ParentExcessBlobGas *math.HexOrDecimal64
ParentBlobGasUsed *math.HexOrDecimal64 ParentBlobGasUsed *math.HexOrDecimal64
SlotNumber *math.HexOrDecimal64
} }
type rejectedTx struct { type rejectedTx struct {
@ -147,15 +149,13 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
isEIP4762 = chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp) isEIP4762 = chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp)
statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre, isEIP4762) statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre, isEIP4762)
signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp) signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp)
gaspool = new(core.GasPool) gaspool = core.NewGasPool(pre.Env.GasLimit)
blockHash = common.Hash{0x13, 0x37} blockHash = common.Hash{0x13, 0x37}
rejectedTxs []*rejectedTx rejectedTxs []*rejectedTx
includedTxs types.Transactions includedTxs types.Transactions
gasUsed = uint64(0)
blobGasUsed = uint64(0) blobGasUsed = uint64(0)
receipts = make(types.Receipts, 0) receipts = make(types.Receipts, 0)
) )
gaspool.AddGas(pre.Env.GasLimit)
vmContext := vm.BlockContext{ vmContext := vm.BlockContext{
CanTransfer: core.CanTransfer, CanTransfer: core.CanTransfer,
Transfer: core.Transfer, Transfer: core.Transfer,
@ -195,6 +195,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
ExcessBlobGas: pre.Env.ParentExcessBlobGas, ExcessBlobGas: pre.Env.ParentExcessBlobGas,
BlobGasUsed: pre.Env.ParentBlobGasUsed, BlobGasUsed: pre.Env.ParentBlobGasUsed,
BaseFee: pre.Env.ParentBaseFee, BaseFee: pre.Env.ParentBaseFee,
SlotNumber: pre.Env.SlotNumber,
} }
header := &types.Header{ header := &types.Header{
Time: pre.Env.Timestamp, Time: pre.Env.Timestamp,
@ -255,14 +256,14 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
statedb.SetTxContext(tx.Hash(), len(receipts)) statedb.SetTxContext(tx.Hash(), len(receipts))
var ( var (
snapshot = statedb.Snapshot() snapshot = statedb.Snapshot()
prevGas = gaspool.Gas() gp = gaspool.Snapshot()
) )
receipt, err := core.ApplyTransactionWithEVM(msg, gaspool, statedb, vmContext.BlockNumber, blockHash, pre.Env.Timestamp, tx, &gasUsed, evm) receipt, err := core.ApplyTransactionWithEVM(msg, gaspool, statedb, vmContext.BlockNumber, blockHash, pre.Env.Timestamp, tx, evm)
if err != nil { if err != nil {
statedb.RevertToSnapshot(snapshot) statedb.RevertToSnapshot(snapshot)
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err) log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err)
rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()})
gaspool.SetGas(prevGas) gaspool.Set(gp)
continue continue
} }
if receipt.Logs == nil { if receipt.Logs == nil {
@ -349,7 +350,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
Receipts: receipts, Receipts: receipts,
Rejected: rejectedTxs, Rejected: rejectedTxs,
Difficulty: (*math.HexOrDecimal256)(vmContext.Difficulty), Difficulty: (*math.HexOrDecimal256)(vmContext.Difficulty),
GasUsed: (math.HexOrDecimal64)(gasUsed), GasUsed: (math.HexOrDecimal64)(gaspool.Used()),
BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee), BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee),
} }
if pre.Env.Withdrawals != nil { if pre.Env.Withdrawals != nil {

View file

@ -38,6 +38,7 @@ func (h header) MarshalJSON() ([]byte, error) {
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
SlotNumber *math.HexOrDecimal64 `json:"slotNumber" rlp:"optional"`
} }
var enc header var enc header
enc.ParentHash = h.ParentHash enc.ParentHash = h.ParentHash
@ -60,6 +61,7 @@ func (h header) MarshalJSON() ([]byte, error) {
enc.BlobGasUsed = (*math.HexOrDecimal64)(h.BlobGasUsed) enc.BlobGasUsed = (*math.HexOrDecimal64)(h.BlobGasUsed)
enc.ExcessBlobGas = (*math.HexOrDecimal64)(h.ExcessBlobGas) enc.ExcessBlobGas = (*math.HexOrDecimal64)(h.ExcessBlobGas)
enc.ParentBeaconBlockRoot = h.ParentBeaconBlockRoot enc.ParentBeaconBlockRoot = h.ParentBeaconBlockRoot
enc.SlotNumber = (*math.HexOrDecimal64)(h.SlotNumber)
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -86,6 +88,7 @@ func (h *header) UnmarshalJSON(input []byte) error {
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
SlotNumber *math.HexOrDecimal64 `json:"slotNumber" rlp:"optional"`
} }
var dec header var dec header
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -155,5 +158,8 @@ func (h *header) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconBlockRoot != nil { if dec.ParentBeaconBlockRoot != nil {
h.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot h.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot
} }
if dec.SlotNumber != nil {
h.SlotNumber = (*uint64)(dec.SlotNumber)
}
return nil return nil
} }

View file

@ -37,6 +37,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"` ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"` ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
SlotNumber *math.HexOrDecimal64 `json:"slotNumber"`
} }
var enc stEnv var enc stEnv
enc.Coinbase = common.UnprefixedAddress(s.Coinbase) enc.Coinbase = common.UnprefixedAddress(s.Coinbase)
@ -59,6 +60,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
enc.ParentExcessBlobGas = (*math.HexOrDecimal64)(s.ParentExcessBlobGas) enc.ParentExcessBlobGas = (*math.HexOrDecimal64)(s.ParentExcessBlobGas)
enc.ParentBlobGasUsed = (*math.HexOrDecimal64)(s.ParentBlobGasUsed) enc.ParentBlobGasUsed = (*math.HexOrDecimal64)(s.ParentBlobGasUsed)
enc.ParentBeaconBlockRoot = s.ParentBeaconBlockRoot enc.ParentBeaconBlockRoot = s.ParentBeaconBlockRoot
enc.SlotNumber = (*math.HexOrDecimal64)(s.SlotNumber)
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -85,6 +87,7 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"` ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"` ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
SlotNumber *math.HexOrDecimal64 `json:"slotNumber"`
} }
var dec stEnv var dec stEnv
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -154,5 +157,8 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconBlockRoot != nil { if dec.ParentBeaconBlockRoot != nil {
s.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot s.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot
} }
if dec.SlotNumber != nil {
s.SlotNumber = (*uint64)(dec.SlotNumber)
}
return nil return nil
} }

View file

@ -27,7 +27,9 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
@ -177,9 +179,12 @@ func Transaction(ctx *cli.Context) error {
r.Error = errors.New("gas * maxFeePerGas exceeds 256 bits") r.Error = errors.New("gas * maxFeePerGas exceeds 256 bits")
} }
// Check whether the init code size has been exceeded. // Check whether the init code size has been exceeded.
if chainConfig.IsShanghai(new(big.Int), 0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { if tx.To() == nil {
r.Error = errors.New("max initcode size exceeded") if err := vm.CheckMaxInitCodeSize(&rules, uint64(len(tx.Data()))); err != nil {
r.Error = err
}
} }
if chainConfig.IsOsaka(new(big.Int), 0) && tx.Gas() > params.MaxTxGas { if chainConfig.IsOsaka(new(big.Int), 0) && tx.Gas() > params.MaxTxGas {
r.Error = errors.New("gas limit exceeds maximum") r.Error = errors.New("gas limit exceeds maximum")
} }

177
cmd/fetchpayload/main.go Normal file
View file

@ -0,0 +1,177 @@
// Copyright 2026 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// fetchpayload queries an Ethereum node over RPC, fetches a block and its
// execution witness, and writes the combined Payload (ChainID + Block +
// Witness) to disk in the format consumed by cmd/keeper.
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"math/big"
"os"
"path/filepath"
"strings"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
)
// Payload is duplicated from cmd/keeper/main.go (package main, not importable).
type Payload struct {
ChainID uint64
Block *types.Block
Witness *stateless.Witness
}
func main() {
var (
rpcURL = flag.String("rpc", "http://localhost:8545", "RPC endpoint URL")
blockArg = flag.String("block", "latest", `Block number: decimal, 0x-hex, or "latest"`)
format = flag.String("format", "rlp", "Comma-separated output formats: rlp, hex, json")
outDir = flag.String("out", "", "Output directory (default: current directory)")
)
flag.Parse()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Parse block number (nil means "latest" in ethclient).
blockNum, err := parseBlockNumber(*blockArg)
if err != nil {
fatal("invalid block number %q: %v", *blockArg, err)
}
// Connect to the node.
client, err := ethclient.DialContext(ctx, *rpcURL)
if err != nil {
fatal("failed to connect to %s: %v", *rpcURL, err)
}
defer client.Close()
chainID, err := client.ChainID(ctx)
if err != nil {
fatal("failed to get chain ID: %v", err)
}
// Fetch the block first so we have a concrete number for the witness call,
// avoiding a race where "latest" advances between the two RPCs.
block, err := client.BlockByNumber(ctx, blockNum)
if err != nil {
fatal("failed to fetch block: %v", err)
}
fmt.Printf("Fetched block %d (%#x)\n", block.NumberU64(), block.Hash())
// Fetch the execution witness via the debug namespace.
var extWitness stateless.ExtWitness
err = client.Client().CallContext(ctx, &extWitness, "debug_executionWitness", rpc.BlockNumber(block.NumberU64()))
if err != nil {
fatal("failed to fetch execution witness: %v", err)
}
witness := new(stateless.Witness)
err = witness.FromExtWitness(&extWitness)
if err != nil {
fatal("failed to convert witness: %v", err)
}
payload := Payload{
ChainID: chainID.Uint64(),
Block: block,
Witness: witness,
}
// Encode payload as RLP (shared by "rlp" and "hex" formats).
rlpBytes, err := rlp.EncodeToBytes(payload)
if err != nil {
fatal("failed to RLP-encode payload: %v", err)
}
// Write one output file per requested format.
blockHex := fmt.Sprintf("%x", block.NumberU64())
for f := range strings.SplitSeq(*format, ",") {
f = strings.TrimSpace(f)
outPath := filepath.Join(*outDir, fmt.Sprintf("%s_payload.%s", blockHex, f))
var data []byte
switch f {
case "rlp":
data = rlpBytes
case "hex":
data = []byte(hexutil.Encode(rlpBytes))
case "json":
data, err = marshalJSONPayload(chainID, block, &extWitness)
if err != nil {
fatal("failed to JSON-encode payload: %v", err)
}
default:
fatal("unknown format %q (valid: rlp, hex, json)", f)
}
if err := os.WriteFile(outPath, data, 0644); err != nil {
fatal("failed to write %s: %v", outPath, err)
}
fmt.Printf("Wrote %s (%d bytes)\n", outPath, len(data))
}
}
// parseBlockNumber converts a CLI string to *big.Int.
// Returns nil for "latest" (ethclient convention for the head block).
func parseBlockNumber(s string) (*big.Int, error) {
if strings.EqualFold(s, "latest") {
return nil, nil
}
n := new(big.Int)
if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") {
if _, ok := n.SetString(s[2:], 16); !ok {
return nil, fmt.Errorf("invalid hex number")
}
return n, nil
}
if _, ok := n.SetString(s, 10); !ok {
return nil, fmt.Errorf("invalid decimal number")
}
return n, nil
}
// jsonPayload is a JSON-friendly representation of Payload. It uses ExtWitness
// instead of the internal Witness (which has no JSON marshaling).
type jsonPayload struct {
ChainID uint64 `json:"chainId"`
Block *types.Block `json:"block"`
Witness *stateless.ExtWitness `json:"witness"`
}
func marshalJSONPayload(chainID *big.Int, block *types.Block, ext *stateless.ExtWitness) ([]byte, error) {
return json.MarshalIndent(jsonPayload{
ChainID: chainID.Uint64(),
Block: block,
Witness: ext,
}, "", " ")
}
func fatal(format string, args ...any) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
os.Exit(1)
}

View file

@ -111,6 +111,7 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
utils.MetricsInfluxDBUsernameFlag, utils.MetricsInfluxDBUsernameFlag,
utils.MetricsInfluxDBPasswordFlag, utils.MetricsInfluxDBPasswordFlag,
utils.MetricsInfluxDBTagsFlag, utils.MetricsInfluxDBTagsFlag,
utils.MetricsInfluxDBIntervalFlag,
utils.MetricsInfluxDBTokenFlag, utils.MetricsInfluxDBTokenFlag,
utils.MetricsInfluxDBBucketFlag, utils.MetricsInfluxDBBucketFlag,
utils.MetricsInfluxDBOrganizationFlag, utils.MetricsInfluxDBOrganizationFlag,
@ -219,13 +220,19 @@ This command dumps out the state for a given block (or latest, if none provided)
pruneHistoryCommand = &cli.Command{ pruneHistoryCommand = &cli.Command{
Action: pruneHistory, Action: pruneHistory,
Name: "prune-history", Name: "prune-history",
Usage: "Prune blockchain history (block bodies and receipts) up to the merge block", Usage: "Prune blockchain history (block bodies and receipts) up to a specified point",
ArgsUsage: "", ArgsUsage: "",
Flags: utils.DatabaseFlags, Flags: slices.Concat(utils.DatabaseFlags, []cli.Flag{
utils.ChainHistoryFlag,
}),
Description: ` Description: `
The prune-history command removes historical block bodies and receipts from the The prune-history command removes historical block bodies and receipts from the
blockchain database up to the merge block, while preserving block headers. This blockchain database up to a specified point, while preserving block headers. This
helps reduce storage requirements for nodes that don't need full historical data.`, helps reduce storage requirements for nodes that don't need full historical data.
The --history.chain flag is required to specify the pruning target:
- postmerge: Prune up to the merge block. The node will keep the merge block and everything thereafter.
- postprague: Prune up to the Prague (Pectra) upgrade block. The node will keep the prague block and everything thereafter.`,
} }
downloadEraCommand = &cli.Command{ downloadEraCommand = &cli.Command{
@ -787,47 +794,74 @@ func hashish(x string) bool {
} }
func pruneHistory(ctx *cli.Context) error { func pruneHistory(ctx *cli.Context) error {
// Parse and validate the history mode flag.
if !ctx.IsSet(utils.ChainHistoryFlag.Name) {
return errors.New("--history.chain flag is required")
}
var mode history.HistoryMode
if err := mode.UnmarshalText([]byte(ctx.String(utils.ChainHistoryFlag.Name))); err != nil {
return err
}
if mode == history.KeepAll {
return errors.New("--history.chain=all is not valid for pruning. To restore history, use 'geth import-history'")
}
stack, _ := makeConfigNode(ctx) stack, _ := makeConfigNode(ctx)
defer stack.Close() defer stack.Close()
// Open the chain database // Open the chain database.
chain, chaindb := utils.MakeChain(ctx, stack, false) chain, chaindb := utils.MakeChain(ctx, stack, false)
defer chaindb.Close() defer chaindb.Close()
defer chain.Stop() defer chain.Stop()
// Determine the prune point. This will be the first PoS block. // Determine the prune point based on the history mode.
prunePoint, ok := history.PrunePoints[chain.Genesis().Hash()] genesisHash := chain.Genesis().Hash()
if !ok || prunePoint == nil { prunePoint := history.GetPrunePoint(genesisHash, mode)
return errors.New("prune point not found") if prunePoint == nil {
return fmt.Errorf("prune point for %q not found for this network", mode.String())
} }
var ( var (
mergeBlock = prunePoint.BlockNumber targetBlock = prunePoint.BlockNumber
mergeBlockHash = prunePoint.BlockHash.Hex() targetBlockHash = prunePoint.BlockHash
) )
// Check we're far enough past merge to ensure all data is in freezer // Check the current freezer tail to see if pruning is needed/possible.
freezerTail, _ := chaindb.Tail()
if freezerTail > 0 {
if freezerTail == targetBlock {
log.Info("Database already pruned to target block", "tail", freezerTail)
return nil
}
if freezerTail > targetBlock {
// Database is pruned beyond the target - can't unprune.
return fmt.Errorf("database is already pruned to block %d, which is beyond target %d. Cannot unprune. To restore history, use 'geth import-history'", freezerTail, targetBlock)
}
// freezerTail < targetBlock: we can prune further, continue below.
}
// Check we're far enough past the target to ensure all data is in freezer.
currentHeader := chain.CurrentHeader() currentHeader := chain.CurrentHeader()
if currentHeader == nil { if currentHeader == nil {
return errors.New("current header not found") return errors.New("current header not found")
} }
if currentHeader.Number.Uint64() < mergeBlock+params.FullImmutabilityThreshold { if currentHeader.Number.Uint64() < targetBlock+params.FullImmutabilityThreshold {
return fmt.Errorf("chain not far enough past merge block, need %d more blocks", return fmt.Errorf("chain not far enough past target block %d, need %d more blocks",
mergeBlock+params.FullImmutabilityThreshold-currentHeader.Number.Uint64()) targetBlock, targetBlock+params.FullImmutabilityThreshold-currentHeader.Number.Uint64())
} }
// Double-check the prune block in db has the expected hash. // Double-check the target block in db has the expected hash.
hash := rawdb.ReadCanonicalHash(chaindb, mergeBlock) hash := rawdb.ReadCanonicalHash(chaindb, targetBlock)
if hash != common.HexToHash(mergeBlockHash) { if hash != targetBlockHash {
return fmt.Errorf("merge block hash mismatch: got %s, want %s", hash.Hex(), mergeBlockHash) return fmt.Errorf("target block hash mismatch: got %s, want %s", hash.Hex(), targetBlockHash.Hex())
} }
log.Info("Starting history pruning", "head", currentHeader.Number, "tail", mergeBlock, "tailHash", mergeBlockHash) log.Info("Starting history pruning", "head", currentHeader.Number, "target", targetBlock, "targetHash", targetBlockHash.Hex())
start := time.Now() start := time.Now()
rawdb.PruneTransactionIndex(chaindb, mergeBlock) rawdb.PruneTransactionIndex(chaindb, targetBlock)
if _, err := chaindb.TruncateTail(mergeBlock); err != nil { if _, err := chaindb.TruncateTail(targetBlock); err != nil {
return fmt.Errorf("failed to truncate ancient data: %v", err) return fmt.Errorf("failed to truncate ancient data: %v", err)
} }
log.Info("History pruning completed", "tail", mergeBlock, "elapsed", common.PrettyDuration(time.Since(start))) log.Info("History pruning completed", "tail", targetBlock, "elapsed", common.PrettyDuration(time.Since(start)))
// TODO(s1na): what if there is a crash between the two prune operations? // TODO(s1na): what if there is a crash between the two prune operations?

View file

@ -377,6 +377,9 @@ func applyMetricConfig(ctx *cli.Context, cfg *gethConfig) {
if ctx.IsSet(utils.MetricsInfluxDBTagsFlag.Name) { if ctx.IsSet(utils.MetricsInfluxDBTagsFlag.Name) {
cfg.Metrics.InfluxDBTags = ctx.String(utils.MetricsInfluxDBTagsFlag.Name) cfg.Metrics.InfluxDBTags = ctx.String(utils.MetricsInfluxDBTagsFlag.Name)
} }
if ctx.IsSet(utils.MetricsInfluxDBIntervalFlag.Name) {
cfg.Metrics.InfluxDBInterval = ctx.Duration(utils.MetricsInfluxDBIntervalFlag.Name)
}
if ctx.IsSet(utils.MetricsEnableInfluxDBV2Flag.Name) { if ctx.IsSet(utils.MetricsEnableInfluxDBV2Flag.Name) {
cfg.Metrics.EnableInfluxDBV2 = ctx.Bool(utils.MetricsEnableInfluxDBV2Flag.Name) cfg.Metrics.EnableInfluxDBV2 = ctx.Bool(utils.MetricsEnableInfluxDBV2Flag.Name)
} }

View file

@ -30,7 +30,7 @@ import (
) )
const ( const (
ipcAPIs = "admin:1.0 debug:1.0 engine:1.0 eth:1.0 miner:1.0 net:1.0 rpc:1.0 txpool:1.0 web3:1.0" ipcAPIs = "admin:1.0 debug:1.0 engine:1.0 eth:1.0 miner:1.0 net:1.0 rpc:1.0 testing:1.0 txpool:1.0 web3:1.0"
httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0" httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0"
) )

View file

@ -19,6 +19,7 @@ package main
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"math"
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
@ -37,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/tablewriter"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
@ -51,7 +53,24 @@ var (
} }
removeChainDataFlag = &cli.BoolFlag{ removeChainDataFlag = &cli.BoolFlag{
Name: "remove.chain", Name: "remove.chain",
Usage: "If set, selects the state data for removal", Usage: "If set, selects the chain data for removal",
}
inspectTrieTopFlag = &cli.IntFlag{
Name: "top",
Usage: "Print the top N results per ranking category",
Value: 10,
}
inspectTrieDumpPathFlag = &cli.StringFlag{
Name: "dump-path",
Usage: "Path for the trie statistics dump file",
}
inspectTrieSummarizeFlag = &cli.StringFlag{
Name: "summarize",
Usage: "Summarize an existing trie dump file (skip trie traversal)",
}
inspectTrieContractFlag = &cli.StringFlag{
Name: "contract",
Usage: "Inspect only the storage of the given contract address (skips full account trie walk)",
} }
removedbCommand = &cli.Command{ removedbCommand = &cli.Command{
@ -74,6 +93,7 @@ Remove blockchain and state databases`,
dbCompactCmd, dbCompactCmd,
dbGetCmd, dbGetCmd,
dbDeleteCmd, dbDeleteCmd,
dbInspectTrieCmd,
dbPutCmd, dbPutCmd,
dbGetSlotsCmd, dbGetSlotsCmd,
dbDumpFreezerIndex, dbDumpFreezerIndex,
@ -92,6 +112,22 @@ Remove blockchain and state databases`,
Usage: "Inspect the storage size for each type of data in the database", Usage: "Inspect the storage size for each type of data in the database",
Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`, Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`,
} }
dbInspectTrieCmd = &cli.Command{
Action: inspectTrie,
Name: "inspect-trie",
ArgsUsage: "<blocknum>",
Flags: slices.Concat([]cli.Flag{
utils.ExcludeStorageFlag,
inspectTrieTopFlag,
utils.OutputFileFlag,
inspectTrieDumpPathFlag,
inspectTrieSummarizeFlag,
inspectTrieContractFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Usage: "Print detailed trie information about the structure of account trie and storage tries.",
Description: `This commands iterates the entrie trie-backed state. If the 'blocknum' is not specified,
the latest block number will be used by default.`,
}
dbCheckStateContentCmd = &cli.Command{ dbCheckStateContentCmd = &cli.Command{
Action: checkStateContent, Action: checkStateContent,
Name: "check-state-content", Name: "check-state-content",
@ -385,6 +421,88 @@ func checkStateContent(ctx *cli.Context) error {
return nil return nil
} }
func inspectTrie(ctx *cli.Context) error {
topN := ctx.Int(inspectTrieTopFlag.Name)
if topN <= 0 {
return fmt.Errorf("invalid --%s value %d (must be > 0)", inspectTrieTopFlag.Name, topN)
}
config := &trie.InspectConfig{
NoStorage: ctx.Bool(utils.ExcludeStorageFlag.Name),
TopN: topN,
Path: ctx.String(utils.OutputFileFlag.Name),
}
if summarizePath := ctx.String(inspectTrieSummarizeFlag.Name); summarizePath != "" {
if ctx.NArg() > 0 {
return fmt.Errorf("block number argument is not supported with --%s", inspectTrieSummarizeFlag.Name)
}
config.DumpPath = summarizePath
log.Info("Summarizing trie dump", "path", summarizePath, "top", topN)
return trie.Summarize(summarizePath, config)
}
if ctx.NArg() > 1 {
return fmt.Errorf("excessive number of arguments: %v", ctx.Command.ArgsUsage)
}
stack, _ := makeConfigNode(ctx)
db := utils.MakeChainDatabase(ctx, stack, false)
defer stack.Close()
defer db.Close()
var (
trieRoot common.Hash
hash common.Hash
number uint64
)
switch {
case ctx.NArg() == 0 || ctx.Args().Get(0) == "latest":
head := rawdb.ReadHeadHeaderHash(db)
n, ok := rawdb.ReadHeaderNumber(db, head)
if !ok {
return fmt.Errorf("could not load head block hash")
}
number = n
case ctx.Args().Get(0) == "snapshot":
trieRoot = rawdb.ReadSnapshotRoot(db)
number = math.MaxUint64
default:
var err error
number, err = strconv.ParseUint(ctx.Args().Get(0), 10, 64)
if err != nil {
return fmt.Errorf("failed to parse blocknum, Args[0]: %v, err: %v", ctx.Args().Get(0), err)
}
}
if number != math.MaxUint64 {
hash = rawdb.ReadCanonicalHash(db, number)
if hash == (common.Hash{}) {
return fmt.Errorf("canonical hash for block %d not found", number)
}
blockHeader := rawdb.ReadHeader(db, hash, number)
trieRoot = blockHeader.Root
}
if trieRoot == (common.Hash{}) {
log.Error("Empty root hash")
}
config.DumpPath = ctx.String(inspectTrieDumpPathFlag.Name)
if config.DumpPath == "" {
config.DumpPath = stack.ResolvePath("trie-dump.bin")
}
triedb := utils.MakeTrieDatabase(ctx, stack, db, false, true, false)
defer triedb.Close()
if contractAddr := ctx.String(inspectTrieContractFlag.Name); contractAddr != "" {
address := common.HexToAddress(contractAddr)
log.Info("Inspecting contract", "address", address, "root", trieRoot, "block", number)
return trie.InspectContract(triedb, db, trieRoot, address)
}
log.Info("Inspecting trie", "root", trieRoot, "block", number, "dump", config.DumpPath, "top", topN)
return trie.Inspect(triedb, trieRoot, config)
}
func showDBStats(db ethdb.KeyValueStater) { func showDBStats(db ethdb.KeyValueStater) {
stats, err := db.Stat() stats, err := db.Stat()
if err != nil { if err != nil {
@ -759,7 +877,7 @@ func showMetaData(ctx *cli.Context) error {
data = append(data, []string{"headHeader.Root", fmt.Sprintf("%v", h.Root)}) data = append(data, []string{"headHeader.Root", fmt.Sprintf("%v", h.Root)})
data = append(data, []string{"headHeader.Number", fmt.Sprintf("%d (%#x)", h.Number, h.Number)}) data = append(data, []string{"headHeader.Number", fmt.Sprintf("%d (%#x)", h.Number, h.Number)})
} }
table := rawdb.NewTableWriter(os.Stdout) table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Field", "Value"}) table.SetHeader([]string{"Field", "Value"})
table.AppendBulk(data) table.AppendBulk(data)
table.Render() table.Render()

View file

@ -22,7 +22,6 @@ import (
"os" "os"
"slices" "slices"
"sort" "sort"
"strconv"
"time" "time"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
@ -216,6 +215,7 @@ var (
utils.MetricsInfluxDBUsernameFlag, utils.MetricsInfluxDBUsernameFlag,
utils.MetricsInfluxDBPasswordFlag, utils.MetricsInfluxDBPasswordFlag,
utils.MetricsInfluxDBTagsFlag, utils.MetricsInfluxDBTagsFlag,
utils.MetricsInfluxDBIntervalFlag,
utils.MetricsEnableInfluxDBV2Flag, utils.MetricsEnableInfluxDBV2Flag,
utils.MetricsInfluxDBTokenFlag, utils.MetricsInfluxDBTokenFlag,
utils.MetricsInfluxDBBucketFlag, utils.MetricsInfluxDBBucketFlag,
@ -316,18 +316,6 @@ func prepare(ctx *cli.Context) {
case !ctx.IsSet(utils.NetworkIdFlag.Name): case !ctx.IsSet(utils.NetworkIdFlag.Name):
log.Info("Starting Geth on Ethereum mainnet...") log.Info("Starting Geth on Ethereum mainnet...")
} }
// If we're a full node on mainnet without --cache specified, bump default cache allowance
if !ctx.IsSet(utils.CacheFlag.Name) && !ctx.IsSet(utils.NetworkIdFlag.Name) {
// Make sure we're not on any supported preconfigured testnet either
if !ctx.IsSet(utils.HoleskyFlag.Name) &&
!ctx.IsSet(utils.SepoliaFlag.Name) &&
!ctx.IsSet(utils.HoodiFlag.Name) &&
!ctx.IsSet(utils.DeveloperFlag.Name) {
// Nope, we're really on mainnet. Bump that cache up!
log.Info("Bumping default cache on mainnet", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 4096)
ctx.Set(utils.CacheFlag.Name, strconv.Itoa(4096))
}
}
} }
// geth is the main entry point into the system if no special subcommand is run. // geth is the main entry point into the system if no special subcommand is run.

View file

@ -13,11 +13,11 @@ require (
github.com/bits-and-blooms/bitset v1.20.0 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/consensys/gnark-crypto v0.18.1 // indirect github.com/consensys/gnark-crypto v0.18.1 // indirect
github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect github.com/crate-crypto/go-eth-kzg v1.5.0 // indirect
github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/emicklei/dot v1.6.2 // indirect github.com/emicklei/dot v1.6.2 // indirect
github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.6 // indirect
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect
github.com/ferranbt/fastssz v0.1.4 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
@ -31,16 +31,16 @@ require (
github.com/minio/sha256-simd v1.0.0 // indirect github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect github.com/supranational/blst v0.3.16 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect github.com/tklauser/numcpus v0.6.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.40.0 // indirect
golang.org/x/crypto v0.44.0 // indirect golang.org/x/crypto v0.44.0 // indirect
golang.org/x/sync v0.18.0 // indirect golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.39.0 // indirect golang.org/x/sys v0.40.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )

View file

@ -28,8 +28,8 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAK
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/consensys/gnark-crypto v0.18.1 h1:RyLV6UhPRoYYzaFnPQA4qK3DyuDgkTgskDdoGqFt3fI= github.com/consensys/gnark-crypto v0.18.1 h1:RyLV6UhPRoYYzaFnPQA4qK3DyuDgkTgskDdoGqFt3fI=
github.com/consensys/gnark-crypto v0.18.1/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c= github.com/consensys/gnark-crypto v0.18.1/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c=
github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg= github.com/crate-crypto/go-eth-kzg v1.5.0 h1:FYRiJMJG2iv+2Dy3fi14SVGjcPteZ5HAAUe4YWlJygc=
github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/crate-crypto/go-eth-kzg v1.5.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
@ -40,8 +40,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A=
github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s= github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn27fRjSls=
github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs= github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw=
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
@ -111,22 +111,22 @@ github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw= github.com/supranational/blst v0.3.16 h1:bTDadT+3fK497EvLdWRQEjiGnUtzJ7jjIUMF0jqwYhE=
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/supranational/blst v0.3.16/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
@ -137,8 +137,8 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=

View file

@ -218,7 +218,11 @@ var (
Usage: "Max number of elements (0 = no limit)", Usage: "Max number of elements (0 = no limit)",
Value: 0, Value: 0,
} }
OutputFileFlag = &cli.StringFlag{
Name: "output",
Usage: "Writes the result in json to the output",
Value: "",
}
SnapshotFlag = &cli.BoolFlag{ SnapshotFlag = &cli.BoolFlag{
Name: "snapshot", Name: "snapshot",
Usage: `Enables snapshot-database mode (default = enable)`, Usage: `Enables snapshot-database mode (default = enable)`,
@ -315,7 +319,7 @@ var (
} }
ChainHistoryFlag = &cli.StringFlag{ ChainHistoryFlag = &cli.StringFlag{
Name: "history.chain", Name: "history.chain",
Usage: `Blockchain history retention ("all" or "postmerge")`, Usage: `Blockchain history retention ("all", "postmerge", or "postprague")`,
Value: ethconfig.Defaults.HistoryMode.String(), Value: ethconfig.Defaults.HistoryMode.String(),
Category: flags.StateCategory, Category: flags.StateCategory,
} }
@ -480,8 +484,8 @@ var (
// Performance tuning settings // Performance tuning settings
CacheFlag = &cli.IntFlag{ CacheFlag = &cli.IntFlag{
Name: "cache", Name: "cache",
Usage: "Megabytes of memory allocated to internal caching (default = 4096 mainnet full node, 128 light mode)", Usage: "Megabytes of memory allocated to internal caching",
Value: 1024, Value: 4096,
Category: flags.PerfCategory, Category: flags.PerfCategory,
} }
CacheDatabaseFlag = &cli.IntFlag{ CacheDatabaseFlag = &cli.IntFlag{
@ -1016,6 +1020,13 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
Category: flags.MetricsCategory, Category: flags.MetricsCategory,
} }
MetricsInfluxDBIntervalFlag = &cli.DurationFlag{
Name: "metrics.influxdb.interval",
Usage: "Interval between metrics reports to InfluxDB (with time unit, e.g. 10s)",
Value: metrics.DefaultConfig.InfluxDBInterval,
Category: flags.MetricsCategory,
}
MetricsEnableInfluxDBV2Flag = &cli.BoolFlag{ MetricsEnableInfluxDBV2Flag = &cli.BoolFlag{
Name: "metrics.influxdbv2", Name: "metrics.influxdbv2",
Usage: "Enable metrics export/push to an external InfluxDB v2 database", Usage: "Enable metrics export/push to an external InfluxDB v2 database",
@ -2246,13 +2257,14 @@ func SetupMetrics(cfg *metrics.Config) {
bucket = cfg.InfluxDBBucket bucket = cfg.InfluxDBBucket
organization = cfg.InfluxDBOrganization organization = cfg.InfluxDBOrganization
tagsMap = SplitTagsFlag(cfg.InfluxDBTags) tagsMap = SplitTagsFlag(cfg.InfluxDBTags)
interval = cfg.InfluxDBInterval
) )
if enableExport { if enableExport {
log.Info("Enabling metrics export to InfluxDB") log.Info("Enabling metrics export to InfluxDB", "interval", interval)
go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "geth.", tagsMap) go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, interval, endpoint, database, username, password, "geth.", tagsMap)
} else if enableExportV2 { } else if enableExportV2 {
log.Info("Enabling metrics export to InfluxDB (v2)") log.Info("Enabling metrics export to InfluxDB (v2)", "interval", interval)
go influxdb.InfluxDBV2WithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, token, bucket, organization, "geth.", tagsMap) go influxdb.InfluxDBV2WithTags(metrics.DefaultRegistry, interval, endpoint, token, bucket, organization, "geth.", tagsMap)
} }
// Expvar exporter. // Expvar exporter.
@ -2457,8 +2469,6 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
} }
vmcfg := vm.Config{ vmcfg := vm.Config{
EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name), EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name),
EnableWitnessStats: ctx.Bool(VMWitnessStatsFlag.Name),
StatelessSelfValidation: ctx.Bool(VMStatelessSelfValidationFlag.Name) || ctx.Bool(VMWitnessStatsFlag.Name),
} }
if ctx.IsSet(VMTraceFlag.Name) { if ctx.IsSet(VMTraceFlag.Name) {
if name := ctx.String(VMTraceFlag.Name); name != "" { if name := ctx.String(VMTraceFlag.Name); name != "" {
@ -2472,6 +2482,9 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
} }
options.VmConfig = vmcfg options.VmConfig = vmcfg
options.StatelessSelfValidation = ctx.Bool(VMStatelessSelfValidationFlag.Name) || ctx.Bool(VMWitnessStatsFlag.Name)
options.EnableWitnessStats = ctx.Bool(VMWitnessStatsFlag.Name)
chain, err := core.NewBlockChain(chainDb, gspec, engine, options) chain, err := core.NewBlockChain(chainDb, gspec, engine, options)
if err != nil { if err != nil {
Fatalf("Can't create BlockChain: %v", err) Fatalf("Can't create BlockChain: %v", err)

View file

@ -272,6 +272,14 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
return err return err
} }
} }
amsterdam := chain.Config().IsAmsterdam(header.Number, header.Time)
if amsterdam && header.SlotNumber == nil {
return errors.New("header is missing slotNumber")
}
if !amsterdam && header.SlotNumber != nil {
return fmt.Errorf("invalid slotNumber: have %d, expected nil", *header.SlotNumber)
}
return nil return nil
} }

View file

@ -310,6 +310,8 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", *header.BlobGasUsed) return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", *header.BlobGasUsed)
case header.ParentBeaconRoot != nil: case header.ParentBeaconRoot != nil:
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", *header.ParentBeaconRoot) return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", *header.ParentBeaconRoot)
case header.SlotNumber != nil:
return fmt.Errorf("invalid slotNumber, have %#x, expected nil", *header.SlotNumber)
} }
// All basic checks passed, verify cascading fields // All basic checks passed, verify cascading fields
return c.verifyCascadingFields(chain, header, parents) return c.verifyCascadingFields(chain, header, parents)
@ -694,6 +696,9 @@ func encodeSigHeader(w io.Writer, header *types.Header) {
if header.ParentBeaconRoot != nil { if header.ParentBeaconRoot != nil {
panic("unexpected parent beacon root value in clique") panic("unexpected parent beacon root value in clique")
} }
if header.SlotNumber != nil {
panic("unexpected slot number value in clique")
}
if err := rlp.Encode(w, enc); err != nil { if err := rlp.Encode(w, enc); err != nil {
panic("can't encode: " + err.Error()) panic("can't encode: " + err.Error())
} }

View file

@ -283,6 +283,8 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa
return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", *header.BlobGasUsed) return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", *header.BlobGasUsed)
case header.ParentBeaconRoot != nil: case header.ParentBeaconRoot != nil:
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", *header.ParentBeaconRoot) return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", *header.ParentBeaconRoot)
case header.SlotNumber != nil:
return fmt.Errorf("invalid slotNumber, have %#x, expected nil", *header.SlotNumber)
} }
// Add some fake checks for tests // Add some fake checks for tests
if ethash.fakeDelay != nil { if ethash.fakeDelay != nil {
@ -559,6 +561,9 @@ func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
if header.ParentBeaconRoot != nil { if header.ParentBeaconRoot != nil {
panic("parent beacon root set on ethash") panic("parent beacon root set on ethash")
} }
if header.SlotNumber != nil {
panic("slot number set on ethash")
}
rlp.Encode(hasher, enc) rlp.Encode(hasher, enc)
hasher.Sum(hash[:0]) hasher.Sum(hash[:0])
return hash return hash

View file

@ -282,7 +282,7 @@ func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, str
for ; start > 0; start-- { for ; start > 0; start-- {
// Skip all methods and namespaces (i.e. including the dot) // Skip all methods and namespaces (i.e. including the dot)
c := line[start] c := line[start]
if c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '1' && c <= '9') { if c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') {
continue continue
} }
// We've hit an unexpected character, autocomplete form here // We've hit an unexpected character, autocomplete form here

View file

@ -93,9 +93,7 @@ var (
accountReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/account/single/reads", nil) accountReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/account/single/reads", nil)
storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil) storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil)
codeReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/code/single/reads", nil) codeReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/code/single/reads", nil)
triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil)
snapshotCommitTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil)
triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil)
blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil) blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil)
blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil) blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil)
@ -219,6 +217,10 @@ type BlockChainConfig struct {
// detailed statistics will be logged. Negative value means disabled (default), // detailed statistics will be logged. Negative value means disabled (default),
// zero logs all blocks, positive value filters blocks by execution time. // zero logs all blocks, positive value filters blocks by execution time.
SlowBlockThreshold time.Duration SlowBlockThreshold time.Duration
// Execution configs
StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose)
EnableWitnessStats bool // Whether trie access statistics collection is enabled
} }
// DefaultConfig returns the default config. // DefaultConfig returns the default config.
@ -322,7 +324,7 @@ type BlockChain struct {
lastWrite uint64 // Last block when the state was flushed lastWrite uint64 // Last block when the state was flushed
flushInterval atomic.Int64 // Time interval (processing time) after which to flush a state flushInterval atomic.Int64 // Time interval (processing time) after which to flush a state
triedb *triedb.Database // The database handler for maintaining trie nodes. triedb *triedb.Database // The database handler for maintaining trie nodes.
statedb *state.CachingDB // State database to reuse between imports (contains state cache) codedb *state.CodeDB // The database handler for maintaining contract codes.
txIndexer *txIndexer // Transaction indexer, might be nil if not enabled txIndexer *txIndexer // Transaction indexer, might be nil if not enabled
hc *HeaderChain hc *HeaderChain
@ -404,6 +406,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
cfg: cfg, cfg: cfg,
db: db, db: db,
triedb: triedb, triedb: triedb,
codedb: state.NewCodeDB(db),
triegc: prque.New[int64, common.Hash](nil), triegc: prque.New[int64, common.Hash](nil),
chainmu: syncx.NewClosableMutex(), chainmu: syncx.NewClosableMutex(),
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit), bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
@ -420,7 +423,6 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
return nil, err return nil, err
} }
bc.flushInterval.Store(int64(cfg.TrieTimeLimit)) bc.flushInterval.Store(int64(cfg.TrieTimeLimit))
bc.statedb = state.NewDatabase(bc.triedb, nil)
bc.validator = NewBlockValidator(chainConfig, bc) bc.validator = NewBlockValidator(chainConfig, bc)
bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc) bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc)
bc.processor = NewStateProcessor(bc.hc) bc.processor = NewStateProcessor(bc.hc)
@ -597,9 +599,6 @@ func (bc *BlockChain) setupSnapshot() {
AsyncBuild: !bc.cfg.SnapshotWait, AsyncBuild: !bc.cfg.SnapshotWait,
} }
bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root) bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root)
// Re-initialize the state database with snapshot
bc.statedb = state.NewDatabase(bc.triedb, bc.snaps)
} }
} }
@ -716,8 +715,12 @@ func (bc *BlockChain) loadLastState() error {
// initializeHistoryPruning sets bc.historyPrunePoint. // initializeHistoryPruning sets bc.historyPrunePoint.
func (bc *BlockChain) initializeHistoryPruning(latest uint64) error { func (bc *BlockChain) initializeHistoryPruning(latest uint64) error {
freezerTail, _ := bc.db.Tail() var (
freezerTail, _ = bc.db.Tail()
genesisHash = bc.genesisBlock.Hash()
mergePoint = history.MergePrunePoints[genesisHash]
praguePoint = history.PraguePrunePoints[genesisHash]
)
switch bc.cfg.ChainHistoryMode { switch bc.cfg.ChainHistoryMode {
case history.KeepAll: case history.KeepAll:
if freezerTail == 0 { if freezerTail == 0 {
@ -725,33 +728,65 @@ func (bc *BlockChain) initializeHistoryPruning(latest uint64) error {
} }
// The database was pruned somehow, so we need to figure out if it's a known // The database was pruned somehow, so we need to figure out if it's a known
// configuration or an error. // configuration or an error.
predefinedPoint := history.PrunePoints[bc.genesisBlock.Hash()] if mergePoint != nil && freezerTail == mergePoint.BlockNumber {
if predefinedPoint == nil || freezerTail != predefinedPoint.BlockNumber { bc.historyPrunePoint.Store(mergePoint)
log.Error("Chain history database is pruned with unknown configuration", "tail", freezerTail) return nil
return errors.New("unexpected database tail")
} }
bc.historyPrunePoint.Store(predefinedPoint) if praguePoint != nil && freezerTail == praguePoint.BlockNumber {
return nil bc.historyPrunePoint.Store(praguePoint)
return nil
}
log.Error("Chain history database is pruned with unknown configuration", "tail", freezerTail)
return errors.New("unexpected database tail")
case history.KeepPostMerge: case history.KeepPostMerge:
if mergePoint == nil {
return errors.New("history pruning requested for unknown network")
}
if freezerTail == 0 && latest != 0 { if freezerTail == 0 && latest != 0 {
// This is the case where a user is trying to run with --history.chain
// postmerge directly on an existing DB. We could just trigger the pruning
// here, but it'd be a bit dangerous since they may not have intended this
// action to happen. So just tell them how to do it.
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cfg.ChainHistoryMode.String())) log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cfg.ChainHistoryMode.String()))
log.Error(fmt.Sprintf("Run 'geth prune-history' to prune pre-merge history.")) log.Error("Run 'geth prune-history --history.chain postmerge' to prune pre-merge history.")
return errors.New("history pruning requested via configuration") return errors.New("history pruning requested via configuration")
} }
predefinedPoint := history.PrunePoints[bc.genesisBlock.Hash()] // Check if DB is pruned further than requested (to Prague).
if predefinedPoint == nil { if praguePoint != nil && freezerTail == praguePoint.BlockNumber {
log.Error("Chain history pruning is not supported for this network", "genesis", bc.genesisBlock.Hash()) log.Error("Chain history database is pruned to Prague block, but postmerge mode was requested.")
log.Error("History cannot be unpruned. To restore history, use 'geth import-history'.")
log.Error("If you intended to keep post-Prague history, use '--history.chain postprague' instead.")
return errors.New("database pruned beyond requested history mode")
}
if freezerTail > 0 && freezerTail != mergePoint.BlockNumber {
return errors.New("chain history database pruned to unknown block")
}
bc.historyPrunePoint.Store(mergePoint)
return nil
case history.KeepPostPrague:
if praguePoint == nil {
return errors.New("history pruning requested for unknown network") return errors.New("history pruning requested for unknown network")
} else if freezerTail > 0 && freezerTail != predefinedPoint.BlockNumber { }
// Check if already at the prague prune point.
if freezerTail == praguePoint.BlockNumber {
bc.historyPrunePoint.Store(praguePoint)
return nil
}
// Check if database needs pruning.
if latest != 0 {
if freezerTail == 0 {
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cfg.ChainHistoryMode.String()))
log.Error("Run 'geth prune-history --history.chain postprague' to prune pre-Prague history.")
return errors.New("history pruning requested via configuration")
}
if mergePoint != nil && freezerTail == mergePoint.BlockNumber {
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is only pruned to merge block.", bc.cfg.ChainHistoryMode.String()))
log.Error("Run 'geth prune-history --history.chain postprague' to prune pre-Prague history.")
return errors.New("history pruning requested via configuration")
}
log.Error("Chain history database is pruned to unknown block", "tail", freezerTail) log.Error("Chain history database is pruned to unknown block", "tail", freezerTail)
return errors.New("unexpected database tail") return errors.New("unexpected database tail")
} }
bc.historyPrunePoint.Store(predefinedPoint) // Fresh database (latest == 0), will sync from prague point.
bc.historyPrunePoint.Store(praguePoint)
return nil return nil
default: default:
@ -1279,6 +1314,8 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error {
func (bc *BlockChain) writeHeadBlock(block *types.Block) { func (bc *BlockChain) writeHeadBlock(block *types.Block) {
// Add the block to the canonical chain number scheme and mark as the head // Add the block to the canonical chain number scheme and mark as the head
batch := bc.db.NewBatch() batch := bc.db.NewBatch()
defer batch.Close()
rawdb.WriteHeadHeaderHash(batch, block.Hash()) rawdb.WriteHeadHeaderHash(batch, block.Hash())
rawdb.WriteHeadFastBlockHash(batch, block.Hash()) rawdb.WriteHeadFastBlockHash(batch, block.Hash())
rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64()) rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64())
@ -1653,6 +1690,8 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
batch = bc.db.NewBatch() batch = bc.db.NewBatch()
start = time.Now() start = time.Now()
) )
defer batch.Close()
rawdb.WriteBlock(batch, block) rawdb.WriteBlock(batch, block)
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts) rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts)
rawdb.WritePreimages(batch, statedb.Preimages()) rawdb.WritePreimages(batch, statedb.Preimages())
@ -1990,7 +2029,15 @@ func (bc *BlockChain) insertChain(ctx context.Context, chain types.Blocks, setHe
} }
// The traced section of block import. // The traced section of block import.
start := time.Now() start := time.Now()
res, err := bc.ProcessBlock(ctx, parent.Root, block, setHead, makeWitness && len(chain) == 1) config := ExecuteConfig{
WriteState: true,
WriteHead: setHead,
EnableTracer: true,
MakeWitness: makeWitness && len(chain) == 1,
StatelessSelfValidation: bc.cfg.StatelessSelfValidation,
EnableWitnessStats: bc.cfg.EnableWitnessStats,
}
res, err := bc.ProcessBlock(ctx, parent.Root, block, config)
if err != nil { if err != nil {
return nil, it.index, err return nil, it.index, err
} }
@ -2073,19 +2120,47 @@ func (bpr *blockProcessingResult) Stats() *ExecuteStats {
return bpr.stats return bpr.stats
} }
// ExecuteConfig defines optional behaviors during execution.
type ExecuteConfig struct {
// WriteState controls whether the computed state changes are persisted to
// the underlying storage. If false, execution is performed in-memory only.
WriteState bool
// WriteHead indicates whether the execution result should update the canonical
// chain head. It's only relevant with WriteState == True.
WriteHead bool
// EnableTracer enables execution tracing. This is typically used for debugging
// or analysis and may significantly impact performance.
EnableTracer bool
// MakeWitness indicates whether to generate execution witness data during
// execution. Enabling this may introduce additional memory and CPU overhead.
MakeWitness bool
// StatelessSelfValidation indicates whether the execution witnesses generation
// and self-validation (testing purpose) is enabled.
StatelessSelfValidation bool
// EnableWitnessStats indicates whether to enable collection of witness trie
// access statistics
EnableWitnessStats bool
}
// ProcessBlock executes and validates the given block. If there was no error // ProcessBlock executes and validates the given block. If there was no error
// it writes the block and associated state to database. // it writes the block and associated state to database.
func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (result *blockProcessingResult, blockEndErr error) { func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, config ExecuteConfig) (result *blockProcessingResult, blockEndErr error) {
var ( var (
err error err error
startTime = time.Now() startTime = time.Now()
statedb *state.StateDB statedb *state.StateDB
interrupt atomic.Bool interrupt atomic.Bool
sdb = state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps)
) )
defer interrupt.Store(true) // terminate the prefetch at the end defer interrupt.Store(true) // terminate the prefetch at the end
if bc.cfg.NoPrefetch { if bc.cfg.NoPrefetch {
statedb, err = state.New(parentRoot, bc.statedb) statedb, err = state.New(parentRoot, sdb)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2095,23 +2170,27 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
// //
// Note: the main processor and prefetcher share the same reader with a local // Note: the main processor and prefetcher share the same reader with a local
// cache for mitigating the overhead of state access. // cache for mitigating the overhead of state access.
prefetch, process, err := bc.statedb.ReadersWithCacheStats(parentRoot) prefetch, process, err := sdb.ReadersWithCacheStats(parentRoot)
if err != nil { if err != nil {
return nil, err return nil, err
} }
throwaway, err := state.NewWithReader(parentRoot, bc.statedb, prefetch) throwaway, err := state.NewWithReader(parentRoot, sdb, prefetch)
if err != nil { if err != nil {
return nil, err return nil, err
} }
statedb, err = state.NewWithReader(parentRoot, bc.statedb, process) statedb, err = state.NewWithReader(parentRoot, sdb, process)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Upload the statistics of reader at the end // Upload the statistics of reader at the end
defer func() { defer func() {
if result != nil { if result != nil {
result.stats.StatePrefetchCacheStats = prefetch.GetStats() if stater, ok := prefetch.(state.ReaderStater); ok {
result.stats.StateReadCacheStats = process.GetStats() result.stats.StatePrefetchCacheStats = stater.GetStats()
}
if stater, ok := process.(state.ReaderStater); ok {
result.stats.StateReadCacheStats = stater.GetStats()
}
} }
}() }()
go func(start time.Time, throwaway *state.StateDB, block *types.Block) { go func(start time.Time, throwaway *state.StateDB, block *types.Block) {
@ -2138,12 +2217,12 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
// Generate witnesses either if we're self-testing, or if it's the // Generate witnesses either if we're self-testing, or if it's the
// only block being inserted. A bit crude, but witnesses are huge, // only block being inserted. A bit crude, but witnesses are huge,
// so we refuse to make an entire chain of them. // so we refuse to make an entire chain of them.
if bc.cfg.VmConfig.StatelessSelfValidation || makeWitness { if config.StatelessSelfValidation || config.MakeWitness {
witness, err = stateless.NewWitness(block.Header(), bc) witness, err = stateless.NewWitness(block.Header(), bc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if bc.cfg.VmConfig.EnableWitnessStats { if config.EnableWitnessStats {
witnessStats = stateless.NewWitnessStats() witnessStats = stateless.NewWitnessStats()
} }
} }
@ -2151,17 +2230,20 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
defer statedb.StopPrefetcher() defer statedb.StopPrefetcher()
} }
if bc.logger != nil && bc.logger.OnBlockStart != nil { // Instrument the blockchain tracing
bc.logger.OnBlockStart(tracing.BlockEvent{ if config.EnableTracer {
Block: block, if bc.logger != nil && bc.logger.OnBlockStart != nil {
Finalized: bc.CurrentFinalBlock(), bc.logger.OnBlockStart(tracing.BlockEvent{
Safe: bc.CurrentSafeBlock(), Block: block,
}) Finalized: bc.CurrentFinalBlock(),
} Safe: bc.CurrentSafeBlock(),
if bc.logger != nil && bc.logger.OnBlockEnd != nil { })
defer func() { }
bc.logger.OnBlockEnd(blockEndErr) if bc.logger != nil && bc.logger.OnBlockEnd != nil {
}() defer func() {
bc.logger.OnBlockEnd(blockEndErr)
}()
}
} }
// Process block using the parent state as reference point // Process block using the parent state as reference point
@ -2191,7 +2273,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
// witness builder/runner, which would otherwise be impossible due to the // witness builder/runner, which would otherwise be impossible due to the
// various invalid chain states/behaviors being contained in those tests. // various invalid chain states/behaviors being contained in those tests.
xvstart := time.Now() xvstart := time.Now()
if witness := statedb.Witness(); witness != nil && bc.cfg.VmConfig.StatelessSelfValidation { if witness := statedb.Witness(); witness != nil && config.StatelessSelfValidation {
log.Warn("Running stateless self-validation", "block", block.Number(), "hash", block.Hash()) log.Warn("Running stateless self-validation", "block", block.Number(), "hash", block.Hash())
// Remove critical computed fields from the block to force true recalculation // Remove critical computed fields from the block to force true recalculation
@ -2244,31 +2326,28 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
stats.CrossValidation = xvtime // The time spent on stateless cross validation stats.CrossValidation = xvtime // The time spent on stateless cross validation
// Write the block to the chain and get the status. // Write the block to the chain and get the status.
var ( var status WriteStatus
wstart = time.Now() if config.WriteState {
status WriteStatus wstart := time.Now()
) if !config.WriteHead {
if !setHead { // Don't set the head, only insert the block
// Don't set the head, only insert the block err = bc.writeBlockWithState(block, res.Receipts, statedb)
err = bc.writeBlockWithState(block, res.Receipts, statedb) } else {
} else { status, err = bc.writeBlockAndSetHead(block, res.Receipts, res.Logs, statedb, false)
status, err = bc.writeBlockAndSetHead(block, res.Receipts, res.Logs, statedb, false) }
} if err != nil {
if err != nil { return nil, err
return nil, err }
// Update the metrics touched during block commit
stats.AccountCommits = statedb.AccountCommits // Account commits are complete, we can mark them
stats.StorageCommits = statedb.StorageCommits // Storage commits are complete, we can mark them
stats.DatabaseCommit = statedb.DatabaseCommits // Database commits are complete, we can mark them
stats.BlockWrite = time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.DatabaseCommits
} }
// Report the collected witness statistics // Report the collected witness statistics
if witnessStats != nil { if witnessStats != nil {
witnessStats.ReportMetrics(block.NumberU64()) witnessStats.ReportMetrics(block.NumberU64())
} }
// Update the metrics touched during block commit
stats.AccountCommits = statedb.AccountCommits // Account commits are complete, we can mark them
stats.StorageCommits = statedb.StorageCommits // Storage commits are complete, we can mark them
stats.SnapshotCommit = statedb.SnapshotCommits // Snapshot commits are complete, we can mark them
stats.TrieDBCommit = statedb.TrieDBCommits // Trie database commits are complete, we can mark them
stats.BlockWrite = time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits
elapsed := time.Since(startTime) + 1 // prevent zero division elapsed := time.Since(startTime) + 1 // prevent zero division
stats.TotalTime = elapsed stats.TotalTime = elapsed
stats.MgasPerSecond = float64(res.GasUsed) * 1000 / float64(elapsed) stats.MgasPerSecond = float64(res.GasUsed) * 1000 / float64(elapsed)
@ -2626,6 +2705,8 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
// Delete useless indexes right now which includes the non-canonical // Delete useless indexes right now which includes the non-canonical
// transaction indexes, canonical chain indexes which above the head. // transaction indexes, canonical chain indexes which above the head.
batch := bc.db.NewBatch() batch := bc.db.NewBatch()
defer batch.Close()
for _, tx := range types.HashDifference(deletedTxs, rebirthTxs) { for _, tx := range types.HashDifference(deletedTxs, rebirthTxs) {
rawdb.DeleteTxLookupEntry(batch, tx) rawdb.DeleteTxLookupEntry(batch, tx)
} }

View file

@ -371,7 +371,7 @@ func (bc *BlockChain) TxIndexDone() bool {
// HasState checks if state trie is fully present in the database or not. // HasState checks if state trie is fully present in the database or not.
func (bc *BlockChain) HasState(hash common.Hash) bool { func (bc *BlockChain) HasState(hash common.Hash) bool {
_, err := bc.statedb.OpenTrie(hash) _, err := bc.triedb.NodeReader(hash)
return err == nil return err == nil
} }
@ -403,7 +403,7 @@ func (bc *BlockChain) stateRecoverable(root common.Hash) bool {
func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) []byte { func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) []byte {
// TODO(rjl493456442) The associated account address is also required // TODO(rjl493456442) The associated account address is also required
// in Verkle scheme. Fix it once snap-sync is supported for Verkle. // in Verkle scheme. Fix it once snap-sync is supported for Verkle.
return bc.statedb.ContractCodeWithPrefix(common.Address{}, hash) return bc.codedb.Reader().CodeWithPrefix(common.Address{}, hash)
} }
// State returns a new mutable state based on the current HEAD block. // State returns a new mutable state based on the current HEAD block.
@ -413,14 +413,14 @@ func (bc *BlockChain) State() (*state.StateDB, error) {
// StateAt returns a new mutable state based on a particular point in time. // StateAt returns a new mutable state based on a particular point in time.
func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) { func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
return state.New(root, bc.statedb) return state.New(root, state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps))
} }
// HistoricState returns a historic state specified by the given root. // HistoricState returns a historic state specified by the given root.
// Live states are not available and won't be served, please use `State` // Live states are not available and won't be served, please use `State`
// or `StateAt` instead. // or `StateAt` instead.
func (bc *BlockChain) HistoricState(root common.Hash) (*state.StateDB, error) { func (bc *BlockChain) HistoricState(root common.Hash) (*state.StateDB, error) {
return state.New(root, state.NewHistoricDatabase(bc.db, bc.triedb)) return state.New(root, state.NewHistoricDatabase(bc.triedb, bc.codedb))
} }
// Config retrieves the chain's fork configuration. // Config retrieves the chain's fork configuration.
@ -444,11 +444,6 @@ func (bc *BlockChain) Processor() Processor {
return bc.processor return bc.processor
} }
// StateCache returns the caching database underpinning the blockchain instance.
func (bc *BlockChain) StateCache() state.Database {
return bc.statedb
}
// GasLimit returns the gas limit of the current HEAD block. // GasLimit returns the gas limit of the current HEAD block.
func (bc *BlockChain) GasLimit() uint64 { func (bc *BlockChain) GasLimit() uint64 {
return bc.CurrentBlock().GasLimit return bc.CurrentBlock().GasLimit
@ -492,6 +487,11 @@ func (bc *BlockChain) TrieDB() *triedb.Database {
return bc.triedb return bc.triedb
} }
// CodeDB retrieves the low level contract code database used for data storage.
func (bc *BlockChain) CodeDB() *state.CodeDB {
return bc.codedb
}
// HeaderChain returns the underlying header chain. // HeaderChain returns the underlying header chain.
func (bc *BlockChain) HeaderChain() *HeaderChain { func (bc *BlockChain) HeaderChain() *HeaderChain {
return bc.hc return bc.hc

View file

@ -30,7 +30,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb/pebble" "github.com/ethereum/go-ethereum/ethdb/pebble"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -2041,7 +2040,6 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
dbconfig.HashDB = hashdb.Defaults dbconfig.HashDB = hashdb.Defaults
} }
chain.triedb = triedb.NewDatabase(chain.db, dbconfig) chain.triedb = triedb.NewDatabase(chain.db, dbconfig)
chain.statedb = state.NewDatabase(chain.triedb, chain.snaps)
// Force run a freeze cycle // Force run a freeze cycle
type freezer interface { type freezer interface {

View file

@ -52,8 +52,7 @@ type ExecuteStats struct {
Execution time.Duration // Time spent on the EVM execution Execution time.Duration // Time spent on the EVM execution
Validation time.Duration // Time spent on the block validation Validation time.Duration // Time spent on the block validation
CrossValidation time.Duration // Optional, time spent on the block cross validation CrossValidation time.Duration // Optional, time spent on the block cross validation
SnapshotCommit time.Duration // Time spent on snapshot commit DatabaseCommit time.Duration // Time spent on database commit
TrieDBCommit time.Duration // Time spent on database commit
BlockWrite time.Duration // Time spent on block write BlockWrite time.Duration // Time spent on block write
TotalTime time.Duration // The total time spent on block execution TotalTime time.Duration // The total time spent on block execution
MgasPerSecond float64 // The million gas processed per second MgasPerSecond float64 // The million gas processed per second
@ -87,22 +86,21 @@ func (s *ExecuteStats) reportMetrics() {
blockExecutionTimer.Update(s.Execution) // The time spent on EVM processing blockExecutionTimer.Update(s.Execution) // The time spent on EVM processing
blockValidationTimer.Update(s.Validation) // The time spent on block validation blockValidationTimer.Update(s.Validation) // The time spent on block validation
blockCrossValidationTimer.Update(s.CrossValidation) // The time spent on stateless cross validation blockCrossValidationTimer.Update(s.CrossValidation) // The time spent on stateless cross validation
snapshotCommitTimer.Update(s.SnapshotCommit) // Snapshot commits are complete, we can mark them triedbCommitTimer.Update(s.DatabaseCommit) // Trie database commits are complete, we can mark them
triedbCommitTimer.Update(s.TrieDBCommit) // Trie database commits are complete, we can mark them
blockWriteTimer.Update(s.BlockWrite) // The time spent on block write blockWriteTimer.Update(s.BlockWrite) // The time spent on block write
blockInsertTimer.Update(s.TotalTime) // The total time spent on block execution blockInsertTimer.Update(s.TotalTime) // The total time spent on block execution
chainMgaspsMeter.Update(time.Duration(s.MgasPerSecond)) // TODO(rjl493456442) generalize the ResettingTimer chainMgaspsMeter.Update(time.Duration(s.MgasPerSecond)) // TODO(rjl493456442) generalize the ResettingTimer
// Cache hit rates // Cache hit rates
accountCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.AccountCacheHit) accountCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.AccountCacheHit)
accountCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.AccountCacheMiss) accountCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.AccountCacheMiss)
storageCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StorageCacheHit) storageCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.StorageCacheHit)
storageCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StorageCacheMiss) storageCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.StorageCacheMiss)
accountCacheHitMeter.Mark(s.StateReadCacheStats.AccountCacheHit) accountCacheHitMeter.Mark(s.StateReadCacheStats.StateStats.AccountCacheHit)
accountCacheMissMeter.Mark(s.StateReadCacheStats.AccountCacheMiss) accountCacheMissMeter.Mark(s.StateReadCacheStats.StateStats.AccountCacheMiss)
storageCacheHitMeter.Mark(s.StateReadCacheStats.StorageCacheHit) storageCacheHitMeter.Mark(s.StateReadCacheStats.StateStats.StorageCacheHit)
storageCacheMissMeter.Mark(s.StateReadCacheStats.StorageCacheMiss) storageCacheMissMeter.Mark(s.StateReadCacheStats.StateStats.StorageCacheMiss)
} }
// slowBlockLog represents the JSON structure for slow block logging. // slowBlockLog represents the JSON structure for slow block logging.
@ -177,14 +175,6 @@ type slowBlockCodeCacheEntry struct {
MissBytes int64 `json:"miss_bytes"` MissBytes int64 `json:"miss_bytes"`
} }
// calculateHitRate computes the cache hit rate as a percentage (0-100).
func calculateHitRate(hits, misses int64) float64 {
if total := hits + misses; total > 0 {
return float64(hits) / float64(total) * 100.0
}
return 0.0
}
// durationToMs converts a time.Duration to milliseconds as a float64 // durationToMs converts a time.Duration to milliseconds as a float64
// with sub-millisecond precision for accurate cross-client metrics. // with sub-millisecond precision for accurate cross-client metrics.
func durationToMs(d time.Duration) float64 { func durationToMs(d time.Duration) float64 {
@ -216,7 +206,7 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
ExecutionMs: durationToMs(s.Execution), ExecutionMs: durationToMs(s.Execution),
StateReadMs: durationToMs(s.AccountReads + s.StorageReads + s.CodeReads), StateReadMs: durationToMs(s.AccountReads + s.StorageReads + s.CodeReads),
StateHashMs: durationToMs(s.AccountHashes + s.AccountUpdates + s.StorageUpdates), StateHashMs: durationToMs(s.AccountHashes + s.AccountUpdates + s.StorageUpdates),
CommitMs: durationToMs(max(s.AccountCommits, s.StorageCommits) + s.TrieDBCommit + s.SnapshotCommit + s.BlockWrite), CommitMs: durationToMs(max(s.AccountCommits, s.StorageCommits) + s.DatabaseCommit + s.BlockWrite),
TotalMs: durationToMs(s.TotalTime), TotalMs: durationToMs(s.TotalTime),
}, },
Throughput: slowBlockThru{ Throughput: slowBlockThru{
@ -238,19 +228,19 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
}, },
Cache: slowBlockCache{ Cache: slowBlockCache{
Account: slowBlockCacheEntry{ Account: slowBlockCacheEntry{
Hits: s.StateReadCacheStats.AccountCacheHit, Hits: s.StateReadCacheStats.StateStats.AccountCacheHit,
Misses: s.StateReadCacheStats.AccountCacheMiss, Misses: s.StateReadCacheStats.StateStats.AccountCacheMiss,
HitRate: calculateHitRate(s.StateReadCacheStats.AccountCacheHit, s.StateReadCacheStats.AccountCacheMiss), HitRate: s.StateReadCacheStats.StateStats.AccountCacheHitRate(),
}, },
Storage: slowBlockCacheEntry{ Storage: slowBlockCacheEntry{
Hits: s.StateReadCacheStats.StorageCacheHit, Hits: s.StateReadCacheStats.StateStats.StorageCacheHit,
Misses: s.StateReadCacheStats.StorageCacheMiss, Misses: s.StateReadCacheStats.StateStats.StorageCacheMiss,
HitRate: calculateHitRate(s.StateReadCacheStats.StorageCacheHit, s.StateReadCacheStats.StorageCacheMiss), HitRate: s.StateReadCacheStats.StateStats.StorageCacheHitRate(),
}, },
Code: slowBlockCodeCacheEntry{ Code: slowBlockCodeCacheEntry{
Hits: s.StateReadCacheStats.CodeStats.CacheHit, Hits: s.StateReadCacheStats.CodeStats.CacheHit,
Misses: s.StateReadCacheStats.CodeStats.CacheMiss, Misses: s.StateReadCacheStats.CodeStats.CacheMiss,
HitRate: calculateHitRate(s.StateReadCacheStats.CodeStats.CacheHit, s.StateReadCacheStats.CodeStats.CacheMiss), HitRate: s.StateReadCacheStats.CodeStats.HitRate(),
HitBytes: s.StateReadCacheStats.CodeStats.CacheHitBytes, HitBytes: s.StateReadCacheStats.CodeStats.CacheHitBytes,
MissBytes: s.StateReadCacheStats.CodeStats.CacheMissBytes, MissBytes: s.StateReadCacheStats.CodeStats.CacheMissBytes,
}, },

View file

@ -157,7 +157,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
} }
return err return err
} }
statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), blockchain.statedb) statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), state.NewDatabase(blockchain.triedb, blockchain.codedb))
if err != nil { if err != nil {
return err return err
} }

View file

@ -63,7 +63,7 @@ func (b *BlockGen) SetCoinbase(addr common.Address) {
panic("coinbase can only be set once") panic("coinbase can only be set once")
} }
b.header.Coinbase = addr b.header.Coinbase = addr
b.gasPool = new(GasPool).AddGas(b.header.GasLimit) b.gasPool = NewGasPool(b.header.GasLimit)
} }
// SetExtra sets the extra data field of the generated block. // SetExtra sets the extra data field of the generated block.
@ -117,10 +117,12 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig) evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig)
) )
b.statedb.SetTxContext(tx.Hash(), len(b.txs)) b.statedb.SetTxContext(tx.Hash(), len(b.txs))
receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed) receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx)
if err != nil { if err != nil {
panic(err) panic(err)
} }
b.header.GasUsed = b.gasPool.Used()
// Merge the tx-local access event into the "block-local" one, in order to collect // Merge the tx-local access event into the "block-local" one, in order to collect
// all values, so that the witness can be built. // all values, so that the witness can be built.
if b.statedb.Database().TrieDB().IsVerkle() { if b.statedb.Database().TrieDB().IsVerkle() {
@ -479,13 +481,14 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int,
if genesis.Config != nil && genesis.Config.IsVerkle(genesis.Config.ChainID, 0) { if genesis.Config != nil && genesis.Config.IsVerkle(genesis.Config.ChainID, 0) {
triedbConfig = triedb.VerkleDefaults triedbConfig = triedb.VerkleDefaults
} }
triedb := triedb.NewDatabase(db, triedbConfig) genesisTriedb := triedb.NewDatabase(db, triedbConfig)
defer triedb.Close() block, err := genesis.Commit(db, genesisTriedb, nil)
_, err := genesis.Commit(db, triedb, nil)
if err != nil { if err != nil {
genesisTriedb.Close()
panic(err) panic(err)
} }
blocks, receipts := GenerateChain(genesis.Config, genesis.ToBlock(), engine, db, n, gen) genesisTriedb.Close()
blocks, receipts := GenerateChain(genesis.Config, block, engine, db, n, gen)
return db, blocks, receipts return db, blocks, receipts
} }

View file

@ -58,6 +58,10 @@ var (
// by a transaction is higher than what's left in the block. // by a transaction is higher than what's left in the block.
ErrGasLimitReached = errors.New("gas limit reached") ErrGasLimitReached = errors.New("gas limit reached")
// ErrGasLimitOverflow is returned by the gas pool if the remaining gas
// exceeds the maximum value of uint64.
ErrGasLimitOverflow = errors.New("gas limit overflow")
// ErrInsufficientFundsForTransfer is returned if the transaction sender doesn't // ErrInsufficientFundsForTransfer is returned if the transaction sender doesn't
// have enough funds for transfer(topmost call only). // have enough funds for transfer(topmost call only).
ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer") ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer")

View file

@ -44,6 +44,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
baseFee *big.Int baseFee *big.Int
blobBaseFee *big.Int blobBaseFee *big.Int
random *common.Hash random *common.Hash
slotNum uint64
) )
// If we don't have an explicit author (i.e. not mining), extract from the header // If we don't have an explicit author (i.e. not mining), extract from the header
@ -61,6 +62,10 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
if header.Difficulty.Sign() == 0 { if header.Difficulty.Sign() == 0 {
random = &header.MixDigest random = &header.MixDigest
} }
if header.SlotNumber != nil {
slotNum = *header.SlotNumber
}
return vm.BlockContext{ return vm.BlockContext{
CanTransfer: CanTransfer, CanTransfer: CanTransfer,
Transfer: Transfer, Transfer: Transfer,
@ -73,6 +78,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
BlobBaseFee: blobBaseFee, BlobBaseFee: blobBaseFee,
GasLimit: header.GasLimit, GasLimit: header.GasLimit,
Random: random, Random: random,
SlotNum: slotNum,
} }
} }

View file

@ -21,39 +21,87 @@ import (
"math" "math"
) )
// GasPool tracks the amount of gas available during execution of the transactions // GasPool tracks the amount of gas available for transaction execution
// in a block. The zero value is a pool with zero gas available. // within a block, along with the cumulative gas consumed.
type GasPool uint64 type GasPool struct {
remaining uint64
initial uint64
cumulativeUsed uint64
}
// AddGas makes gas available for execution. // NewGasPool initializes the gasPool with the given amount.
func (gp *GasPool) AddGas(amount uint64) *GasPool { func NewGasPool(amount uint64) *GasPool {
if uint64(*gp) > math.MaxUint64-amount { return &GasPool{
panic("gas pool pushed above uint64") remaining: amount,
initial: amount,
} }
*(*uint64)(gp) += amount
return gp
} }
// SubGas deducts the given amount from the pool if enough gas is // SubGas deducts the given amount from the pool if enough gas is
// available and returns an error otherwise. // available and returns an error otherwise.
func (gp *GasPool) SubGas(amount uint64) error { func (gp *GasPool) SubGas(amount uint64) error {
if uint64(*gp) < amount { if gp.remaining < amount {
return ErrGasLimitReached return ErrGasLimitReached
} }
*(*uint64)(gp) -= amount gp.remaining -= amount
return nil
}
// ReturnGas adds the refunded gas back to the pool and updates
// the cumulative gas usage accordingly.
func (gp *GasPool) ReturnGas(returned uint64, gasUsed uint64) error {
if gp.remaining > math.MaxUint64-returned {
return fmt.Errorf("%w: remaining: %d, returned: %d", ErrGasLimitOverflow, gp.remaining, returned)
}
// The returned gas calculation differs across forks.
//
// - Pre-Amsterdam:
// returned = purchased - remaining (refund included)
//
// - Post-Amsterdam:
// returned = purchased - gasUsed (refund excluded)
gp.remaining += returned
// gasUsed = max(txGasUsed - gasRefund, calldataFloorGasCost)
// regardless of Amsterdam is activated or not.
gp.cumulativeUsed += gasUsed
return nil return nil
} }
// Gas returns the amount of gas remaining in the pool. // Gas returns the amount of gas remaining in the pool.
func (gp *GasPool) Gas() uint64 { func (gp *GasPool) Gas() uint64 {
return uint64(*gp) return gp.remaining
} }
// SetGas sets the amount of gas with the provided number. // CumulativeUsed returns the amount of cumulative consumed gas (refunded included).
func (gp *GasPool) SetGas(gas uint64) { func (gp *GasPool) CumulativeUsed() uint64 {
*(*uint64)(gp) = gas return gp.cumulativeUsed
}
// Used returns the amount of consumed gas.
func (gp *GasPool) Used() uint64 {
if gp.initial < gp.remaining {
panic("gas used underflow")
}
return gp.initial - gp.remaining
}
// Snapshot returns the deep-copied object as the snapshot.
func (gp *GasPool) Snapshot() *GasPool {
return &GasPool{
initial: gp.initial,
remaining: gp.remaining,
cumulativeUsed: gp.cumulativeUsed,
}
}
// Set sets the content of gasPool with the provided one.
func (gp *GasPool) Set(other *GasPool) {
gp.initial = other.initial
gp.remaining = other.remaining
gp.cumulativeUsed = other.cumulativeUsed
} }
func (gp *GasPool) String() string { func (gp *GasPool) String() string {
return fmt.Sprintf("%d", *gp) return fmt.Sprintf("initial: %d, remaining: %d, cumulative used: %d", gp.initial, gp.remaining, gp.cumulativeUsed)
} }

View file

@ -34,6 +34,7 @@ func (g Genesis) MarshalJSON() ([]byte, error) {
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
SlotNumber *uint64 `json:"slotNumber"`
} }
var enc Genesis var enc Genesis
enc.Config = g.Config enc.Config = g.Config
@ -56,6 +57,7 @@ func (g Genesis) MarshalJSON() ([]byte, error) {
enc.BaseFee = (*math.HexOrDecimal256)(g.BaseFee) enc.BaseFee = (*math.HexOrDecimal256)(g.BaseFee)
enc.ExcessBlobGas = (*math.HexOrDecimal64)(g.ExcessBlobGas) enc.ExcessBlobGas = (*math.HexOrDecimal64)(g.ExcessBlobGas)
enc.BlobGasUsed = (*math.HexOrDecimal64)(g.BlobGasUsed) enc.BlobGasUsed = (*math.HexOrDecimal64)(g.BlobGasUsed)
enc.SlotNumber = g.SlotNumber
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -77,6 +79,7 @@ func (g *Genesis) UnmarshalJSON(input []byte) error {
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
SlotNumber *uint64 `json:"slotNumber"`
} }
var dec Genesis var dec Genesis
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -133,5 +136,8 @@ func (g *Genesis) UnmarshalJSON(input []byte) error {
if dec.BlobGasUsed != nil { if dec.BlobGasUsed != nil {
g.BlobGasUsed = (*uint64)(dec.BlobGasUsed) g.BlobGasUsed = (*uint64)(dec.BlobGasUsed)
} }
if dec.SlotNumber != nil {
g.SlotNumber = dec.SlotNumber
}
return nil return nil
} }

View file

@ -73,6 +73,7 @@ type Genesis struct {
BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559 BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559
ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844 ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844
BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844 BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844
SlotNumber *uint64 `json:"slotNumber"` // EIP-7843
} }
// copy copies the genesis. // copy copies the genesis.
@ -122,6 +123,7 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) {
genesis.BaseFee = genesisHeader.BaseFee genesis.BaseFee = genesisHeader.BaseFee
genesis.ExcessBlobGas = genesisHeader.ExcessBlobGas genesis.ExcessBlobGas = genesisHeader.ExcessBlobGas
genesis.BlobGasUsed = genesisHeader.BlobGasUsed genesis.BlobGasUsed = genesisHeader.BlobGasUsed
genesis.SlotNumber = genesisHeader.SlotNumber
return &genesis, nil return &genesis, nil
} }
@ -547,6 +549,12 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
if conf.IsPrague(num, g.Timestamp) { if conf.IsPrague(num, g.Timestamp) {
head.RequestsHash = &types.EmptyRequestsHash head.RequestsHash = &types.EmptyRequestsHash
} }
if conf.IsAmsterdam(num, g.Timestamp) {
head.SlotNumber = g.SlotNumber
if head.SlotNumber == nil {
head.SlotNumber = new(uint64)
}
}
} }
return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil)) return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil))
} }

View file

@ -308,7 +308,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
}, },
} }
expected := common.FromHex("b94812c1674dcf4f2bc98f4503d15f4cc674265135bcf3be6e4417b60881042a") expected := common.FromHex("1fd154971d9a386c4ec75fe7138c17efb569bfc2962e46e94a376ba997e3fadc")
got := genesis.ToBlock().Root().Bytes() got := genesis.ToBlock().Root().Bytes()
if !bytes.Equal(got, expected) { if !bytes.Equal(got, expected) {
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got) t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)

View file

@ -32,10 +32,13 @@ const (
// KeepPostMerge sets the history pruning point to the merge activation block. // KeepPostMerge sets the history pruning point to the merge activation block.
KeepPostMerge KeepPostMerge
// KeepPostPrague sets the history pruning point to the Prague (Pectra) activation block.
KeepPostPrague
) )
func (m HistoryMode) IsValid() bool { func (m HistoryMode) IsValid() bool {
return m <= KeepPostMerge return m <= KeepPostPrague
} }
func (m HistoryMode) String() string { func (m HistoryMode) String() string {
@ -44,6 +47,8 @@ func (m HistoryMode) String() string {
return "all" return "all"
case KeepPostMerge: case KeepPostMerge:
return "postmerge" return "postmerge"
case KeepPostPrague:
return "postprague"
default: default:
return fmt.Sprintf("invalid HistoryMode(%d)", m) return fmt.Sprintf("invalid HistoryMode(%d)", m)
} }
@ -64,8 +69,10 @@ func (m *HistoryMode) UnmarshalText(text []byte) error {
*m = KeepAll *m = KeepAll
case "postmerge": case "postmerge":
*m = KeepPostMerge *m = KeepPostMerge
case "postprague":
*m = KeepPostPrague
default: default:
return fmt.Errorf(`unknown sync mode %q, want "all" or "postmerge"`, text) return fmt.Errorf(`unknown history mode %q, want "all", "postmerge", or "postprague"`, text)
} }
return nil return nil
} }
@ -75,10 +82,10 @@ type PrunePoint struct {
BlockHash common.Hash BlockHash common.Hash
} }
// PrunePoints the pre-defined history pruning cutoff blocks for known networks. // MergePrunePoints contains the pre-defined history pruning cutoff blocks for known networks.
// They point to the first post-merge block. Any pruning should truncate *up to* but excluding // They point to the first post-merge block. Any pruning should truncate *up to* but excluding
// given block. // the given block.
var PrunePoints = map[common.Hash]*PrunePoint{ var MergePrunePoints = map[common.Hash]*PrunePoint{
// mainnet // mainnet
params.MainnetGenesisHash: { params.MainnetGenesisHash: {
BlockNumber: 15537393, BlockNumber: 15537393,
@ -91,6 +98,39 @@ var PrunePoints = map[common.Hash]*PrunePoint{
}, },
} }
// PraguePrunePoints contains the pre-defined history pruning cutoff blocks for the Prague
// (Pectra) upgrade. They point to the first post-Prague block. Any pruning should truncate
// *up to* but excluding the given block.
var PraguePrunePoints = map[common.Hash]*PrunePoint{
// mainnet - first Prague block (May 7, 2025)
params.MainnetGenesisHash: {
BlockNumber: 22431084,
BlockHash: common.HexToHash("0x50c8cab760b2948349c590461b166773c45d8f4858cccf5a43025ab2960152e8"),
},
// sepolia - first Prague block (March 5, 2025)
params.SepoliaGenesisHash: {
BlockNumber: 7836331,
BlockHash: common.HexToHash("0xe6571beb68bf24dbd8a6ba354518996920c55a3f8d8fdca423e391b8ad071f22"),
},
}
// PrunePoints is an alias for MergePrunePoints for backward compatibility.
// Deprecated: Use GetPrunePoint or MergePrunePoints directly.
var PrunePoints = MergePrunePoints
// GetPrunePoint returns the prune point for the given genesis hash and history mode.
// Returns nil if no prune point is defined for the given combination.
func GetPrunePoint(genesisHash common.Hash, mode HistoryMode) *PrunePoint {
switch mode {
case KeepPostMerge:
return MergePrunePoints[genesisHash]
case KeepPostPrague:
return PraguePrunePoints[genesisHash]
default:
return nil
}
}
// PrunedHistoryError is returned by APIs when the requested history is pruned. // PrunedHistoryError is returned by APIs when the requested history is pruned.
type PrunedHistoryError struct{} type PrunedHistoryError struct{}

View file

@ -424,13 +424,7 @@ func WriteBodyRLP(db ethdb.KeyValueWriter, hash common.Hash, number uint64, rlp
// HasBody verifies the existence of a block body corresponding to the hash. // HasBody verifies the existence of a block body corresponding to the hash.
func HasBody(db ethdb.Reader, hash common.Hash, number uint64) bool { func HasBody(db ethdb.Reader, hash common.Hash, number uint64) bool {
if isCanon(db, number, hash) { if isCanon(db, number, hash) {
// Block is in ancient store, but bodies can be pruned. return true
// Check if the block number is above the pruning tail.
tail, _ := db.Tail()
if number >= tail {
return true
}
return false
} }
if has, err := db.Has(blockBodyKey(number, hash)); !has || err != nil { if has, err := db.Has(blockBodyKey(number, hash)); !has || err != nil {
return false return false
@ -472,13 +466,7 @@ func DeleteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
// to a block. // to a block.
func HasReceipts(db ethdb.Reader, hash common.Hash, number uint64) bool { func HasReceipts(db ethdb.Reader, hash common.Hash, number uint64) bool {
if isCanon(db, number, hash) { if isCanon(db, number, hash) {
// Block is in ancient store, but receipts can be pruned. return true
// Check if the block number is above the pruning tail.
tail, _ := db.Tail()
if number >= tail {
return true
}
return false
} }
if has, err := db.Has(blockReceiptsKey(number, hash)); !has || err != nil { if has, err := db.Has(blockReceiptsKey(number, hash)); !has || err != nil {
return false return false

View file

@ -260,6 +260,46 @@ func basicWrite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
if err != nil { if err != nil {
t.Fatalf("Failed to write ancient data %v", err) t.Fatalf("Failed to write ancient data %v", err)
} }
// Write should work after truncating from tail but over the head
db.TruncateTail(200)
head, err := db.Ancients()
if err != nil {
t.Fatalf("Failed to retrieve head ancients %v", err)
}
tail, err := db.Tail()
if err != nil {
t.Fatalf("Failed to retrieve tail ancients %v", err)
}
if head != 200 || tail != 200 {
t.Fatalf("Ancient head and tail are not expected")
}
_, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
offset := uint64(200)
for i := 0; i < 100; i++ {
if err := op.AppendRaw("a", offset+uint64(i), dataA[i]); err != nil {
return err
}
if err := op.AppendRaw("b", offset+uint64(i), dataB[i]); err != nil {
return err
}
}
return nil
})
if err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
head, err = db.Ancients()
if err != nil {
t.Fatalf("Failed to retrieve head ancients %v", err)
}
tail, err = db.Tail()
if err != nil {
t.Fatalf("Failed to retrieve tail ancients %v", err)
}
if head != 300 || tail != 200 {
t.Fatalf("Ancient head and tail are not expected")
}
} }
func nonMutable(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) { func nonMutable(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {

View file

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/internal/tablewriter"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
@ -477,9 +478,9 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
bodies.add(size) bodies.add(size)
case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength): case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength):
receipts.add(size) receipts.add(size)
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix): case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix) && len(key) == (len(headerPrefix)+8+common.HashLength+len(headerTDSuffix)):
tds.add(size) tds.add(size)
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix): case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix) && len(key) == (len(headerPrefix)+8+common.HashLength+len(headerHashSuffix)):
numHashPairings.add(size) numHashPairings.add(size)
case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength): case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength):
hashNumPairings.add(size) hashNumPairings.add(size)
@ -663,7 +664,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
total.Add(uint64(ancient.size())) total.Add(uint64(ancient.size()))
} }
table := NewTableWriter(os.Stdout) table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Database", "Category", "Size", "Items"}) table.SetHeader([]string{"Database", "Category", "Size", "Items"})
table.SetFooter([]string{"", "Total", common.StorageSize(total.Load()).String(), fmt.Sprintf("%d", count.Load())}) table.SetFooter([]string{"", "Total", common.StorageSize(total.Load()).String(), fmt.Sprintf("%d", count.Load())})
table.AppendBulk(stats) table.AppendBulk(stats)

View file

@ -59,7 +59,7 @@ const freezerTableSize = 2 * 1000 * 1000 * 1000
// - The in-order data ensures that disk reads are always optimized. // - The in-order data ensures that disk reads are always optimized.
type Freezer struct { type Freezer struct {
datadir string datadir string
frozen atomic.Uint64 // Number of items already frozen head atomic.Uint64 // Number of items stored (including items removed from tail)
tail atomic.Uint64 // Number of the first stored item in the freezer tail atomic.Uint64 // Number of the first stored item in the freezer
// This lock synchronizes writers and the truncate operation, as well as // This lock synchronizes writers and the truncate operation, as well as
@ -97,12 +97,12 @@ func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize ui
return nil, errSymlinkDatadir return nil, errSymlinkDatadir
} }
} }
// Leveldb/Pebble uses LOCK as the filelock filename. To prevent the
// name collision, we use FLOCK as the lock name.
flockFile := filepath.Join(datadir, "FLOCK") flockFile := filepath.Join(datadir, "FLOCK")
if err := os.MkdirAll(filepath.Dir(flockFile), 0755); err != nil { if err := os.MkdirAll(filepath.Dir(flockFile), 0755); err != nil {
return nil, err return nil, err
} }
// Leveldb uses LOCK as the filelock filename. To prevent the
// name collision, we use FLOCK as the lock name.
lock := flock.New(flockFile) lock := flock.New(flockFile)
tryLock := lock.TryLock tryLock := lock.TryLock
if readonly { if readonly {
@ -213,7 +213,7 @@ func (f *Freezer) AncientBytes(kind string, id, offset, length uint64) ([]byte,
// Ancients returns the length of the frozen items. // Ancients returns the length of the frozen items.
func (f *Freezer) Ancients() (uint64, error) { func (f *Freezer) Ancients() (uint64, error) {
return f.frozen.Load(), nil return f.head.Load(), nil
} }
// Tail returns the number of first stored item in the freezer. // Tail returns the number of first stored item in the freezer.
@ -252,7 +252,7 @@ func (f *Freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize
defer f.writeLock.Unlock() defer f.writeLock.Unlock()
// Roll back all tables to the starting position in case of error. // Roll back all tables to the starting position in case of error.
prevItem := f.frozen.Load() prevItem := f.head.Load()
defer func() { defer func() {
if err != nil { if err != nil {
// The write operation has failed. Go back to the previous item position. // The write operation has failed. Go back to the previous item position.
@ -273,7 +273,7 @@ func (f *Freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize
if err != nil { if err != nil {
return 0, err return 0, err
} }
f.frozen.Store(item) f.head.Store(item)
return writeSize, nil return writeSize, nil
} }
@ -286,7 +286,7 @@ func (f *Freezer) TruncateHead(items uint64) (uint64, error) {
f.writeLock.Lock() f.writeLock.Lock()
defer f.writeLock.Unlock() defer f.writeLock.Unlock()
oitems := f.frozen.Load() oitems := f.head.Load()
if oitems <= items { if oitems <= items {
return oitems, nil return oitems, nil
} }
@ -295,7 +295,7 @@ func (f *Freezer) TruncateHead(items uint64) (uint64, error) {
return 0, err return 0, err
} }
} }
f.frozen.Store(items) f.head.Store(items)
return oitems, nil return oitems, nil
} }
@ -320,6 +320,11 @@ func (f *Freezer) TruncateTail(tail uint64) (uint64, error) {
} }
} }
f.tail.Store(tail) f.tail.Store(tail)
// Update the head if the requested tail exceeds the current head
if f.head.Load() < tail {
f.head.Store(tail)
}
return old, nil return old, nil
} }
@ -379,7 +384,7 @@ func (f *Freezer) validate() error {
prunedTail = &tmp prunedTail = &tmp
} }
f.frozen.Store(head) f.head.Store(head)
f.tail.Store(*prunedTail) f.tail.Store(*prunedTail)
return nil return nil
} }
@ -414,7 +419,7 @@ func (f *Freezer) repair() error {
} }
} }
f.frozen.Store(head) f.head.Store(head)
f.tail.Store(prunedTail) f.tail.Store(prunedTail)
return nil return nil
} }

View file

@ -113,7 +113,7 @@ func (t *memoryTable) truncateTail(items uint64) error {
return nil return nil
} }
if t.items < items { if t.items < items {
return errors.New("truncation above head") return t.reset(items)
} }
for i := uint64(0); i < items-t.offset; i++ { for i := uint64(0); i < items-t.offset; i++ {
if t.size > uint64(len(t.data[i])) { if t.size > uint64(len(t.data[i])) {
@ -127,6 +127,16 @@ func (t *memoryTable) truncateTail(items uint64) error {
return nil return nil
} }
// reset clears the entire table and sets both the head and tail to the given
// value. It assumes the caller holds the lock and that tail > t.items.
func (t *memoryTable) reset(offset uint64) error {
t.size = 0
t.data = nil
t.items = offset
t.offset = offset
return nil
}
// commit merges the given item batch into table. It's presumed that the // commit merges the given item batch into table. It's presumed that the
// batch is ordered and continuous with table. // batch is ordered and continuous with table.
func (t *memoryTable) commit(batch [][]byte) error { func (t *memoryTable) commit(batch [][]byte) error {
@ -387,6 +397,9 @@ func (f *MemoryFreezer) TruncateTail(tail uint64) (uint64, error) {
} }
} }
f.tail = tail f.tail = tail
if f.items < tail {
f.items = tail
}
return old, nil return old, nil
} }

View file

@ -707,12 +707,13 @@ func (t *freezerTable) truncateTail(items uint64) error {
t.lock.Lock() t.lock.Lock()
defer t.lock.Unlock() defer t.lock.Unlock()
// Ensure the given truncate target falls in the correct range // Short-circuit if the requested tail deletion points to a stale position
if t.itemHidden.Load() >= items { if t.itemHidden.Load() >= items {
return nil return nil
} }
// If the requested tail exceeds the current head, reset the entire table
if t.items.Load() < items { if t.items.Load() < items {
return errors.New("truncation above head") return t.resetTo(items)
} }
// Load the new tail index by the given new tail position // Load the new tail index by the given new tail position
var ( var (
@ -822,10 +823,9 @@ func (t *freezerTable) truncateTail(items uint64) error {
shorten := indexEntrySize * int64(newDeleted-deleted) shorten := indexEntrySize * int64(newDeleted-deleted)
if t.metadata.flushOffset <= shorten { if t.metadata.flushOffset <= shorten {
return fmt.Errorf("invalid index flush offset: %d, shorten: %d", t.metadata.flushOffset, shorten) return fmt.Errorf("invalid index flush offset: %d, shorten: %d", t.metadata.flushOffset, shorten)
} else { }
if err := t.metadata.setFlushOffset(t.metadata.flushOffset-shorten, true); err != nil { if err := t.metadata.setFlushOffset(t.metadata.flushOffset-shorten, true); err != nil {
return err return err
}
} }
// Retrieve the new size and update the total size counter // Retrieve the new size and update the total size counter
newSize, err := t.sizeNolock() newSize, err := t.sizeNolock()
@ -836,6 +836,59 @@ func (t *freezerTable) truncateTail(items uint64) error {
return nil return nil
} }
// resetTo clears the entire table and sets both the head and tail to the given
// value. It assumes the caller holds the lock and that tail > t.items.
func (t *freezerTable) resetTo(tail uint64) error {
// Sync the entire table before resetting, eliminating the potential
// data corruption.
err := t.doSync()
if err != nil {
return err
}
// Update the index file to reflect the new offset
if err := t.index.Close(); err != nil {
return err
}
entry := &indexEntry{
filenum: t.headId + 1,
offset: uint32(tail),
}
if err := reset(t.index.Name(), entry.append(nil)); err != nil {
return err
}
if err := t.metadata.setVirtualTail(tail, true); err != nil {
return err
}
if err := t.metadata.setFlushOffset(indexEntrySize, true); err != nil {
return err
}
t.index, err = openFreezerFileForAppend(t.index.Name())
if err != nil {
return err
}
// Purge all the existing data file
if err := t.head.Close(); err != nil {
return err
}
t.headId = t.headId + 1
t.tailId = t.headId
t.headBytes = 0
t.head, err = t.openFile(t.headId, openFreezerFileTruncated)
if err != nil {
return err
}
t.releaseFilesBefore(t.headId, true)
t.items.Store(tail)
t.itemOffset.Store(tail)
t.itemHidden.Store(tail)
t.sizeGauge.Update(0)
return nil
}
// Close closes all opened files and finalizes the freezer table for use. // Close closes all opened files and finalizes the freezer table for use.
// This operation must be completed before shutdown to prevent the loss of // This operation must be completed before shutdown to prevent the loss of
// recent writes. // recent writes.
@ -1247,25 +1300,20 @@ func (t *freezerTable) doSync() error {
if t.index == nil || t.head == nil || t.metadata.file == nil { if t.index == nil || t.head == nil || t.metadata.file == nil {
return errClosed return errClosed
} }
var err error if err := t.index.Sync(); err != nil {
trackError := func(e error) { return err
if e != nil && err == nil { }
err = e if err := t.head.Sync(); err != nil {
} return err
} }
trackError(t.index.Sync())
trackError(t.head.Sync())
// A crash may occur before the offset is updated, leaving the offset // A crash may occur before the offset is updated, leaving the offset
// points to a old position. If so, the extra items above the offset // points to an old position. If so, the extra items above the offset
// will be truncated during the next run. // will be truncated during the next run.
stat, err := t.index.Stat() stat, err := t.index.Stat()
if err != nil { if err != nil {
return err return err
} }
offset := stat.Size() return t.metadata.setFlushOffset(stat.Size(), true)
trackError(t.metadata.setFlushOffset(offset, true))
return err
} }
func (t *freezerTable) dumpIndexStdout(start, stop int64) { func (t *freezerTable) dumpIndexStdout(start, stop int64) {

View file

@ -1139,6 +1139,7 @@ const (
opTruncateHeadAll opTruncateHeadAll
opTruncateTail opTruncateTail
opTruncateTailAll opTruncateTailAll
opTruncateTailOverHead
opCheckAll opCheckAll
opMax // boundary value, not an actual op opMax // boundary value, not an actual op
) )
@ -1226,6 +1227,11 @@ func (randTest) Generate(r *rand.Rand, size int) reflect.Value {
step.target = deleted + uint64(len(items)) step.target = deleted + uint64(len(items))
items = items[:0] items = items[:0]
deleted = step.target deleted = step.target
case opTruncateTailOverHead:
newDeleted := deleted + uint64(len(items)) + 10
step.target = newDeleted
deleted = newDeleted
items = items[:0]
} }
steps = append(steps, step) steps = append(steps, step)
} }
@ -1268,7 +1274,7 @@ func runRandTest(rt randTest) bool {
for i := 0; i < len(step.items); i++ { for i := 0; i < len(step.items); i++ {
batch.AppendRaw(step.items[i], step.blobs[i]) batch.AppendRaw(step.items[i], step.blobs[i])
} }
batch.commit() rt[i].err = batch.commit()
values = append(values, step.blobs...) values = append(values, step.blobs...)
case opRetrieve: case opRetrieve:
@ -1290,24 +1296,28 @@ func runRandTest(rt randTest) bool {
} }
case opTruncateHead: case opTruncateHead:
f.truncateHead(step.target) rt[i].err = f.truncateHead(step.target)
length := f.items.Load() - f.itemHidden.Load() length := f.items.Load() - f.itemHidden.Load()
values = values[:length] values = values[:length]
case opTruncateHeadAll: case opTruncateHeadAll:
f.truncateHead(step.target) rt[i].err = f.truncateHead(step.target)
values = nil values = nil
case opTruncateTail: case opTruncateTail:
prev := f.itemHidden.Load() prev := f.itemHidden.Load()
f.truncateTail(step.target) rt[i].err = f.truncateTail(step.target)
truncated := f.itemHidden.Load() - prev truncated := f.itemHidden.Load() - prev
values = values[truncated:] values = values[truncated:]
case opTruncateTailAll: case opTruncateTailAll:
f.truncateTail(step.target) rt[i].err = f.truncateTail(step.target)
values = nil
case opTruncateTailOverHead:
rt[i].err = f.truncateTail(step.target)
values = nil values = nil
} }
// Abort the test on error. // Abort the test on error.
@ -1633,3 +1643,43 @@ func TestFreezerAncientBytes(t *testing.T) {
}) })
} }
} }
func TestTruncateOverHead(t *testing.T) {
t.Parallel()
fn := fmt.Sprintf("t-%d", rand.Uint64())
f, err := newTable(os.TempDir(), fn, metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 100, freezerTableConfig{noSnappy: true}, false)
if err != nil {
t.Fatal(err)
}
// Tail truncation on an empty table
if err := f.truncateTail(10); err != nil {
t.Fatal(err)
}
batch := f.newBatch()
data := getChunk(10, 1)
require.NoError(t, batch.AppendRaw(uint64(10), data))
require.NoError(t, batch.commit())
got, err := f.RetrieveItems(uint64(10), 1, 0)
require.NoError(t, err)
if !bytes.Equal(got[0], data) {
t.Fatalf("Unexpected bytes, want: %v, got: %v", data, got[0])
}
// Tail truncation on the non-empty table
if err := f.truncateTail(20); err != nil {
t.Fatal(err)
}
batch = f.newBatch()
data = getChunk(10, 1)
require.NoError(t, batch.AppendRaw(uint64(20), data))
require.NoError(t, batch.commit())
got, err = f.RetrieveItems(uint64(20), 1, 0)
require.NoError(t, err)
if !bytes.Equal(got[0], data) {
t.Fatalf("Unexpected bytes, want: %v, got: %v", data, got[0])
}
}

View file

@ -22,6 +22,19 @@ import (
"path/filepath" "path/filepath"
) )
func atomicRename(src, dest string) error {
if err := os.Rename(src, dest); err != nil {
return err
}
dir, err := os.Open(filepath.Dir(src))
if err != nil {
return err
}
defer dir.Close()
return dir.Sync()
}
// copyFrom copies data from 'srcPath' at offset 'offset' into 'destPath'. // copyFrom copies data from 'srcPath' at offset 'offset' into 'destPath'.
// The 'destPath' is created if it doesn't exist, otherwise it is overwritten. // The 'destPath' is created if it doesn't exist, otherwise it is overwritten.
// Before the copy is executed, there is a callback can be registered to // Before the copy is executed, there is a callback can be registered to
@ -73,13 +86,48 @@ func copyFrom(srcPath, destPath string, offset uint64, before func(f *os.File) e
return err return err
} }
f = nil f = nil
return os.Rename(fname, destPath)
return atomicRename(fname, destPath)
}
// reset atomically replaces the file at the given path with the provided content.
func reset(path string, content []byte) error {
// Create a temp file in the same dir where we want it to wind up
f, err := os.CreateTemp(filepath.Dir(path), "*")
if err != nil {
return err
}
fname := f.Name()
// Clean up the leftover file
defer func() {
if f != nil {
f.Close()
}
os.Remove(fname)
}()
// Write the content into the temp file
_, err = f.Write(content)
if err != nil {
return err
}
// Permanently persist the content into disk
if err := f.Sync(); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
f = nil
return atomicRename(fname, path)
} }
// openFreezerFileForAppend opens a freezer table file and seeks to the end // openFreezerFileForAppend opens a freezer table file and seeks to the end
func openFreezerFileForAppend(filename string) (*os.File, error) { func openFreezerFileForAppend(filename string) (*os.File, error) {
// Open the file without the O_APPEND flag // Open the file without the O_APPEND flag
// because it has differing behaviour during Truncate operations // because it has differing behavior during Truncate operations
// on different OS's // on different OS's
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0644) file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0644)
if err != nil { if err != nil {

View file

@ -253,6 +253,11 @@ func (b *tableBatch) Reset() {
b.batch.Reset() b.batch.Reset()
} }
// Close closes the batch and releases all associated resources.
func (b *tableBatch) Close() {
b.batch.Close()
}
// tableReplayer is a wrapper around a batch replayer which truncates // tableReplayer is a wrapper around a batch replayer which truncates
// the added prefix. // the added prefix.
type tableReplayer struct { type tableReplayer struct {

View file

@ -20,13 +20,13 @@ import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/overlay" "github.com/ethereum/go-ethereum/core/overlay"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie" "github.com/ethereum/go-ethereum/trie/bintrie"
"github.com/ethereum/go-ethereum/trie/transitiontrie" "github.com/ethereum/go-ethereum/trie/transitiontrie"
@ -34,14 +34,6 @@ import (
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
) )
const (
// Number of codehash->size associations to keep.
codeSizeCacheSize = 1_000_000 // 4 megabytes in total
// Cache size granted for caching clean code.
codeCacheSize = 256 * 1024 * 1024
)
// Database wraps access to tries and contract code. // Database wraps access to tries and contract code.
type Database interface { type Database interface {
// Reader returns a state reader associated with the specified state root. // Reader returns a state reader associated with the specified state root.
@ -58,6 +50,11 @@ type Database interface {
// Snapshot returns the underlying state snapshot. // Snapshot returns the underlying state snapshot.
Snapshot() *snapshot.Tree Snapshot() *snapshot.Tree
// Commit flushes all pending writes and finalizes the state transition,
// committing the changes to the underlying storage. It returns an error
// if the commit fails.
Commit(update *stateUpdate) error
} }
// Trie is a Ethereum Merkle Patricia trie. // Trie is a Ethereum Merkle Patricia trie.
@ -149,32 +146,34 @@ type Trie interface {
// state snapshot to provide functionalities for state access. It's meant to be a // state snapshot to provide functionalities for state access. It's meant to be a
// long-live object and has a few caches inside for sharing between blocks. // long-live object and has a few caches inside for sharing between blocks.
type CachingDB struct { type CachingDB struct {
disk ethdb.KeyValueStore triedb *triedb.Database
triedb *triedb.Database codedb *CodeDB
snap *snapshot.Tree snap *snapshot.Tree
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
codeSizeCache *lru.Cache[common.Hash, int]
// Transition-specific fields
TransitionStatePerRoot *lru.Cache[common.Hash, *overlay.TransitionState]
} }
// NewDatabase creates a state database with the provided data sources. // NewDatabase creates a state database with the provided data sources.
func NewDatabase(triedb *triedb.Database, snap *snapshot.Tree) *CachingDB { func NewDatabase(triedb *triedb.Database, codedb *CodeDB) *CachingDB {
if codedb == nil {
codedb = NewCodeDB(triedb.Disk())
}
return &CachingDB{ return &CachingDB{
disk: triedb.Disk(), triedb: triedb,
triedb: triedb, codedb: codedb,
snap: snap,
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
TransitionStatePerRoot: lru.NewCache[common.Hash, *overlay.TransitionState](1000),
} }
} }
// NewDatabaseForTesting is similar to NewDatabase, but it initializes the caching // NewDatabaseForTesting is similar to NewDatabase, but it initializes the caching
// db by using an ephemeral memory db with default config for testing. // db by using an ephemeral memory db with default config for testing.
func NewDatabaseForTesting() *CachingDB { func NewDatabaseForTesting() *CachingDB {
return NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil) db := rawdb.NewMemoryDatabase()
return NewDatabase(triedb.NewDatabase(db, nil), NewCodeDB(db))
}
// WithSnapshot configures the provided contract code cache. Note that this
// registration must be performed before the cachingDB is used.
func (db *CachingDB) WithSnapshot(snapshot *snapshot.Tree) *CachingDB {
db.snap = snapshot
return db
} }
// StateReader returns a state reader associated with the specified state root. // StateReader returns a state reader associated with the specified state root.
@ -218,21 +217,20 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), sr), nil return newReader(db.codedb.Reader(), sr), nil
} }
// ReadersWithCacheStats creates a pair of state readers that share the same // ReadersWithCacheStats creates a pair of state readers that share the same
// underlying state reader and internal state cache, while maintaining separate // underlying state reader and internal state cache, while maintaining separate
// statistics respectively. // statistics respectively.
func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) { func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Reader, error) {
r, err := db.StateReader(stateRoot) r, err := db.StateReader(stateRoot)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
sr := newStateReaderWithCache(r) sr := newStateReaderWithCache(r)
ra := newReader(db.codedb.Reader(), newStateReaderWithStats(sr))
ra := newReaderWithStats(sr, newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache)) rb := newReader(db.codedb.Reader(), newStateReaderWithStats(sr))
rb := newReaderWithStats(sr, newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache))
return ra, rb, nil return ra, rb, nil
} }
@ -268,22 +266,6 @@ func (db *CachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Addre
return tr, nil return tr, nil
} }
// ContractCodeWithPrefix retrieves a particular contract's code. If the
// code can't be found in the cache, then check the existence with **new**
// db scheme.
func (db *CachingDB) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) []byte {
code, _ := db.codeCache.Get(codeHash)
if len(code) > 0 {
return code
}
code = rawdb.ReadCodeWithPrefix(db.disk, codeHash)
if len(code) > 0 {
db.codeCache.Add(codeHash, code)
db.codeSizeCache.Add(codeHash, len(code))
}
return code
}
// TrieDB retrieves any intermediate trie-node caching layer. // TrieDB retrieves any intermediate trie-node caching layer.
func (db *CachingDB) TrieDB() *triedb.Database { func (db *CachingDB) TrieDB() *triedb.Database {
return db.triedb return db.triedb
@ -294,6 +276,40 @@ func (db *CachingDB) Snapshot() *snapshot.Tree {
return db.snap return db.snap
} }
// Commit flushes all pending writes and finalizes the state transition,
// committing the changes to the underlying storage. It returns an error
// if the commit fails.
func (db *CachingDB) Commit(update *stateUpdate) error {
// Short circuit if nothing to commit
if update.empty() {
return nil
}
// Commit dirty contract code if any exists
if len(update.codes) > 0 {
batch := db.codedb.NewBatchWithSize(len(update.codes))
for _, code := range update.codes {
batch.Put(code.hash, code.blob)
}
if err := batch.Commit(); err != nil {
return err
}
}
// If snapshotting is enabled, update the snapshot tree with this new version
if db.snap != nil && db.snap.Snapshot(update.originRoot) != nil {
if err := db.snap.Update(update.root, update.originRoot, update.accounts, update.storages); err != nil {
log.Warn("Failed to update snapshot tree", "from", update.originRoot, "to", update.root, "err", err)
}
// Keep 128 diff layers in the memory, persistent layer is 129th.
// - head layer is paired with HEAD state
// - head-1 layer is paired with HEAD-1 state
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
if err := db.snap.Cap(update.root, TriesInMemory); err != nil {
log.Warn("Failed to cap snapshot tree", "root", update.root, "layers", TriesInMemory, "err", err)
}
}
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet())
}
// mustCopyTrie returns a deep-copied trie. // mustCopyTrie returns a deep-copied trie.
func mustCopyTrie(t Trie) Trie { func mustCopyTrie(t Trie) Trie {
switch t := t.(type) { switch t := t.(type) {

231
core/state/database_code.go Normal file
View file

@ -0,0 +1,231 @@
// Copyright 2026 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package state
import (
"sync/atomic"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
)
const (
// Number of codeHash->size associations to keep.
codeSizeCacheSize = 1_000_000
// Cache size granted for caching clean code.
codeCacheSize = 256 * 1024 * 1024
)
// CodeCache maintains cached contract code that is shared across blocks, enabling
// fast access for external calls such as RPCs and state transitions.
//
// It is thread-safe and has a bounded size.
type codeCache struct {
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
codeSizeCache *lru.Cache[common.Hash, int]
}
// newCodeCache initializes the contract code cache with the predefined capacity.
func newCodeCache() *codeCache {
return &codeCache{
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
}
}
// Get returns the contract code associated with the provided code hash.
func (c *codeCache) Get(hash common.Hash) ([]byte, bool) {
return c.codeCache.Get(hash)
}
// GetSize returns the contract code size associated with the provided code hash.
func (c *codeCache) GetSize(hash common.Hash) (int, bool) {
return c.codeSizeCache.Get(hash)
}
// Put adds the provided contract code along with its size information into the cache.
func (c *codeCache) Put(hash common.Hash, code []byte) {
c.codeCache.Add(hash, code)
c.codeSizeCache.Add(hash, len(code))
}
// CodeReader implements state.ContractCodeReader, accessing contract code either in
// local key-value store or the shared code cache.
//
// Reader is safe for concurrent access.
type CodeReader struct {
db ethdb.KeyValueReader
cache *codeCache
// Cache statistics
hit atomic.Int64 // Number of code lookups found in the cache
miss atomic.Int64 // Number of code lookups not found in the cache
hitBytes atomic.Int64 // Total number of bytes read from cache
missBytes atomic.Int64 // Total number of bytes read from database
}
// newCodeReader constructs the code reader with provided key value store and the cache.
func newCodeReader(db ethdb.KeyValueReader, cache *codeCache) *CodeReader {
return &CodeReader{
db: db,
cache: cache,
}
}
// Has returns the flag indicating whether the contract code with
// specified address and hash exists or not.
func (r *CodeReader) Has(addr common.Address, codeHash common.Hash) bool {
return len(r.Code(addr, codeHash)) > 0
}
// Code implements state.ContractCodeReader, retrieving a particular contract's code.
// Null is returned if the contract code is not present.
func (r *CodeReader) Code(addr common.Address, codeHash common.Hash) []byte {
code, _ := r.cache.Get(codeHash)
if len(code) > 0 {
r.hit.Add(1)
r.hitBytes.Add(int64(len(code)))
return code
}
r.miss.Add(1)
code = rawdb.ReadCode(r.db, codeHash)
if len(code) > 0 {
r.cache.Put(codeHash, code)
r.missBytes.Add(int64(len(code)))
}
return code
}
// CodeSize implements state.ContractCodeReader, retrieving a particular contract
// code's size. Zero is returned if the contract code is not present.
func (r *CodeReader) CodeSize(addr common.Address, codeHash common.Hash) int {
if cached, ok := r.cache.GetSize(codeHash); ok {
r.hit.Add(1)
return cached
}
return len(r.Code(addr, codeHash))
}
// CodeWithPrefix retrieves the contract code for the specified account address
// and code hash. It is almost identical to Code, but uses rawdb.ReadCodeWithPrefix
// for database lookups. The intention is to gradually deprecate the old
// contract code scheme.
func (r *CodeReader) CodeWithPrefix(addr common.Address, codeHash common.Hash) []byte {
code, _ := r.cache.Get(codeHash)
if len(code) > 0 {
r.hit.Add(1)
r.hitBytes.Add(int64(len(code)))
return code
}
r.miss.Add(1)
code = rawdb.ReadCodeWithPrefix(r.db, codeHash)
if len(code) > 0 {
r.cache.Put(codeHash, code)
r.missBytes.Add(int64(len(code)))
}
return code
}
// GetCodeStats implements ContractCodeReaderStater, returning the statistics
// of the code reader.
func (r *CodeReader) GetCodeStats() ContractCodeReaderStats {
return ContractCodeReaderStats{
CacheHit: r.hit.Load(),
CacheMiss: r.miss.Load(),
CacheHitBytes: r.hitBytes.Load(),
CacheMissBytes: r.missBytes.Load(),
}
}
type CodeBatch struct {
db *CodeDB
codes [][]byte
codeHashes []common.Hash
}
// newCodeBatch constructs the batch for writing contract code.
func newCodeBatch(db *CodeDB) *CodeBatch {
return &CodeBatch{
db: db,
}
}
// newCodeBatchWithSize constructs the batch with a pre-allocated capacity.
func newCodeBatchWithSize(db *CodeDB, size int) *CodeBatch {
return &CodeBatch{
db: db,
codes: make([][]byte, 0, size),
codeHashes: make([]common.Hash, 0, size),
}
}
// Put inserts the given contract code into the writer, waiting for commit.
func (b *CodeBatch) Put(codeHash common.Hash, code []byte) {
b.codes = append(b.codes, code)
b.codeHashes = append(b.codeHashes, codeHash)
}
// Commit flushes the accumulated dirty contract code into the database and
// also place them in the cache.
func (b *CodeBatch) Commit() error {
batch := b.db.db.NewBatch()
for i, code := range b.codes {
rawdb.WriteCode(batch, b.codeHashes[i], code)
b.db.cache.Put(b.codeHashes[i], code)
}
if err := batch.Write(); err != nil {
return err
}
b.codes = b.codes[:0]
b.codeHashes = b.codeHashes[:0]
return nil
}
// CodeDB is responsible for managing the contract code and provides the access
// to it. It can be used as a global object, sharing it between multiple entities.
type CodeDB struct {
db ethdb.KeyValueStore
cache *codeCache
}
// NewCodeDB constructs the contract code database with the provided key value store.
func NewCodeDB(db ethdb.KeyValueStore) *CodeDB {
return &CodeDB{
db: db,
cache: newCodeCache(),
}
}
// Reader returns the contract code reader.
func (d *CodeDB) Reader() *CodeReader {
return newCodeReader(d.db, d.cache)
}
// NewBatch returns the batch for flushing contract codes.
func (d *CodeDB) NewBatch() *CodeBatch {
return newCodeBatch(d)
}
// NewBatchWithSize returns the batch with pre-allocated capacity.
func (d *CodeDB) NewBatchWithSize(size int) *CodeBatch {
return newCodeBatchWithSize(d, size)
}

View file

@ -17,15 +17,14 @@
package state package state
import ( import (
"errors"
"fmt" "fmt"
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
@ -221,19 +220,15 @@ func (r *historicalTrieReader) Storage(addr common.Address, key common.Hash) (co
// HistoricDB is the implementation of Database interface, with the ability to // HistoricDB is the implementation of Database interface, with the ability to
// access historical state. // access historical state.
type HistoricDB struct { type HistoricDB struct {
disk ethdb.KeyValueStore triedb *triedb.Database
triedb *triedb.Database codedb *CodeDB
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
codeSizeCache *lru.Cache[common.Hash, int]
} }
// NewHistoricDatabase creates a historic state database. // NewHistoricDatabase creates a historic state database.
func NewHistoricDatabase(disk ethdb.KeyValueStore, triedb *triedb.Database) *HistoricDB { func NewHistoricDatabase(triedb *triedb.Database, codedb *CodeDB) *HistoricDB {
return &HistoricDB{ return &HistoricDB{
disk: disk, triedb: triedb,
triedb: triedb, codedb: codedb,
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
} }
} }
@ -258,7 +253,7 @@ func (db *HistoricDB) Reader(stateRoot common.Hash) (Reader, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil return newReader(db.codedb.Reader(), combined), nil
} }
// OpenTrie opens the main account trie. It's not supported by historic database. // OpenTrie opens the main account trie. It's not supported by historic database.
@ -298,3 +293,10 @@ func (db *HistoricDB) TrieDB() *triedb.Database {
func (db *HistoricDB) Snapshot() *snapshot.Tree { func (db *HistoricDB) Snapshot() *snapshot.Tree {
return nil return nil
} }
// Commit flushes all pending writes and finalizes the state transition,
// committing the changes to the underlying storage. It returns an error
// if the commit fails.
func (db *HistoricDB) Commit(update *stateUpdate) error {
return errors.New("not implemented")
}

View file

@ -144,10 +144,7 @@ func (it *nodeIterator) step() error {
} }
if !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) { if !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
it.codeHash = common.BytesToHash(account.CodeHash) it.codeHash = common.BytesToHash(account.CodeHash)
it.code, err = it.state.reader.Code(address, common.BytesToHash(account.CodeHash)) it.code = it.state.reader.Code(address, common.BytesToHash(account.CodeHash))
if err != nil {
return fmt.Errorf("code %x: %v", account.CodeHash, err)
}
if len(it.code) == 0 { if len(it.code) == 0 {
return fmt.Errorf("code is not found: %x", account.CodeHash) return fmt.Errorf("code is not found: %x", account.CodeHash)
} }

View file

@ -18,17 +18,13 @@ package state
import ( import (
"errors" "errors"
"fmt"
"sync" "sync"
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/overlay" "github.com/ethereum/go-ethereum/core/overlay"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie" "github.com/ethereum/go-ethereum/trie/bintrie"
@ -38,55 +34,26 @@ import (
) )
// ContractCodeReader defines the interface for accessing contract code. // ContractCodeReader defines the interface for accessing contract code.
//
// ContractCodeReader is supposed to be thread-safe.
type ContractCodeReader interface { type ContractCodeReader interface {
// Has returns the flag indicating whether the contract code with // Has returns the flag indicating whether the contract code with
// specified address and hash exists or not. // specified address and hash exists or not.
Has(addr common.Address, codeHash common.Hash) bool Has(addr common.Address, codeHash common.Hash) bool
// Code retrieves a particular contract's code. // Code retrieves a particular contract's code. Returns nil code if the
// // requested contract code doesn't exist.
// - Returns nil code along with nil error if the requested contract code Code(addr common.Address, codeHash common.Hash) []byte
// doesn't exist
// - Returns an error only if an unexpected issue occurs
Code(addr common.Address, codeHash common.Hash) ([]byte, error)
// CodeSize retrieves a particular contracts code's size. // CodeSize retrieves a particular contracts code's size. Returns zero code
// // size if the requested contract code doesn't exist.
// - Returns zero code size along with nil error if the requested contract code CodeSize(addr common.Address, codeHash common.Hash) int
// doesn't exist
// - Returns an error only if an unexpected issue occurs
CodeSize(addr common.Address, codeHash common.Hash) (int, error)
}
// ContractCodeReaderStats aggregates statistics for the contract code reader.
type ContractCodeReaderStats struct {
CacheHit int64 // Number of cache hits
CacheMiss int64 // Number of cache misses
CacheHitBytes int64 // Total bytes served from cache
CacheMissBytes int64 // Total bytes read on cache misses
}
// HitRate returns the cache hit rate.
func (s ContractCodeReaderStats) HitRate() float64 {
if s.CacheHit == 0 {
return 0
}
return float64(s.CacheHit) / float64(s.CacheHit+s.CacheMiss)
}
// ContractCodeReaderWithStats extends ContractCodeReader by adding GetStats to
// expose statistics of code reader.
type ContractCodeReaderWithStats interface {
ContractCodeReader
GetStats() ContractCodeReaderStats
} }
// StateReader defines the interface for accessing accounts and storage slots // StateReader defines the interface for accessing accounts and storage slots
// associated with a specific state. // associated with a specific state.
// //
// StateReader is assumed to be thread-safe and implementation must take care // StateReader is supposed to be thread-safe.
// of the concurrency issue by themselves.
type StateReader interface { type StateReader interface {
// Account retrieves the account associated with a particular address. // Account retrieves the account associated with a particular address.
// //
@ -114,119 +81,6 @@ type Reader interface {
StateReader StateReader
} }
// ReaderStats wraps the statistics of reader.
type ReaderStats struct {
AccountCacheHit int64
AccountCacheMiss int64
StorageCacheHit int64
StorageCacheMiss int64
CodeStats ContractCodeReaderStats
}
// String implements fmt.Stringer, returning string format statistics.
func (s ReaderStats) String() string {
var (
accountCacheHitRate float64
storageCacheHitRate float64
)
if s.AccountCacheHit > 0 {
accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100
}
if s.StorageCacheHit > 0 {
storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100
}
msg := fmt.Sprintf("Reader statistics\n")
msg += fmt.Sprintf("account: hit: %d, miss: %d, rate: %.2f\n", s.AccountCacheHit, s.AccountCacheMiss, accountCacheHitRate)
msg += fmt.Sprintf("storage: hit: %d, miss: %d, rate: %.2f\n", s.StorageCacheHit, s.StorageCacheMiss, storageCacheHitRate)
msg += fmt.Sprintf("code: hit: %d(%v), miss: %d(%v), rate: %.2f\n", s.CodeStats.CacheHit, common.StorageSize(s.CodeStats.CacheHitBytes), s.CodeStats.CacheMiss, common.StorageSize(s.CodeStats.CacheMissBytes), s.CodeStats.HitRate())
return msg
}
// ReaderWithStats wraps the additional method to retrieve the reader statistics from.
type ReaderWithStats interface {
Reader
GetStats() ReaderStats
}
// cachingCodeReader implements ContractCodeReader, accessing contract code either in
// local key-value store or the shared code cache.
//
// cachingCodeReader is safe for concurrent access.
type cachingCodeReader struct {
db ethdb.KeyValueReader
// These caches could be shared by multiple code reader instances,
// they are natively thread-safe.
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
codeSizeCache *lru.Cache[common.Hash, int]
// Cache statistics
hit atomic.Int64 // Number of code lookups found in the cache
miss atomic.Int64 // Number of code lookups not found in the cache
hitBytes atomic.Int64 // Total number of bytes read from cache
missBytes atomic.Int64 // Total number of bytes read from database
}
// newCachingCodeReader constructs the code reader.
func newCachingCodeReader(db ethdb.KeyValueReader, codeCache *lru.SizeConstrainedCache[common.Hash, []byte], codeSizeCache *lru.Cache[common.Hash, int]) *cachingCodeReader {
return &cachingCodeReader{
db: db,
codeCache: codeCache,
codeSizeCache: codeSizeCache,
}
}
// Code implements ContractCodeReader, retrieving a particular contract's code.
// If the contract code doesn't exist, no error will be returned.
func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]byte, error) {
code, _ := r.codeCache.Get(codeHash)
if len(code) > 0 {
r.hit.Add(1)
r.hitBytes.Add(int64(len(code)))
return code, nil
}
r.miss.Add(1)
code = rawdb.ReadCode(r.db, codeHash)
if len(code) > 0 {
r.codeCache.Add(codeHash, code)
r.codeSizeCache.Add(codeHash, len(code))
r.missBytes.Add(int64(len(code)))
}
return code, nil
}
// CodeSize implements ContractCodeReader, retrieving a particular contracts code's size.
// If the contract code doesn't exist, no error will be returned.
func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
if cached, ok := r.codeSizeCache.Get(codeHash); ok {
r.hit.Add(1)
return cached, nil
}
code, err := r.Code(addr, codeHash)
if err != nil {
return 0, err
}
return len(code), nil
}
// Has returns the flag indicating whether the contract code with
// specified address and hash exists or not.
func (r *cachingCodeReader) Has(addr common.Address, codeHash common.Hash) bool {
code, _ := r.Code(addr, codeHash)
return len(code) > 0
}
// GetStats returns the statistics of the code reader.
func (r *cachingCodeReader) GetStats() ContractCodeReaderStats {
return ContractCodeReaderStats{
CacheHit: r.hit.Load(),
CacheMiss: r.miss.Load(),
CacheHitBytes: r.hitBytes.Load(),
CacheMissBytes: r.missBytes.Load(),
}
}
// flatReader wraps a database state reader and is safe for concurrent access. // flatReader wraps a database state reader and is safe for concurrent access.
type flatReader struct { type flatReader struct {
reader database.StateReader reader database.StateReader
@ -495,20 +349,6 @@ func (r *multiStateReader) Storage(addr common.Address, slot common.Hash) (commo
return common.Hash{}, errors.Join(errs...) return common.Hash{}, errors.Join(errs...)
} }
// reader is the wrapper of ContractCodeReader and StateReader interface.
type reader struct {
ContractCodeReader
StateReader
}
// newReader constructs a reader with the supplied code reader and state reader.
func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
return &reader{
ContractCodeReader: codeReader,
StateReader: stateReader,
}
}
// stateReaderWithCache is a wrapper around StateReader that maintains additional // stateReaderWithCache is a wrapper around StateReader that maintains additional
// state caches to support concurrent state access. // state caches to support concurrent state access.
type stateReaderWithCache struct { type stateReaderWithCache struct {
@ -619,9 +459,10 @@ func (r *stateReaderWithCache) Storage(addr common.Address, slot common.Hash) (c
return value, err return value, err
} }
type readerWithStats struct { // stateReaderWithStats is a wrapper over the stateReaderWithCache, tracking
// the cache hit statistics of the reader.
type stateReaderWithStats struct {
*stateReaderWithCache *stateReaderWithCache
ContractCodeReaderWithStats
accountCacheHit atomic.Int64 accountCacheHit atomic.Int64
accountCacheMiss atomic.Int64 accountCacheMiss atomic.Int64
@ -629,11 +470,10 @@ type readerWithStats struct {
storageCacheMiss atomic.Int64 storageCacheMiss atomic.Int64
} }
// newReaderWithStats constructs the reader with additional statistics tracked. // newReaderWithStats constructs the state reader with additional statistics tracked.
func newReaderWithStats(sr *stateReaderWithCache, cr ContractCodeReaderWithStats) *readerWithStats { func newStateReaderWithStats(sr *stateReaderWithCache) *stateReaderWithStats {
return &readerWithStats{ return &stateReaderWithStats{
stateReaderWithCache: sr, stateReaderWithCache: sr,
ContractCodeReaderWithStats: cr,
} }
} }
@ -641,7 +481,7 @@ func newReaderWithStats(sr *stateReaderWithCache, cr ContractCodeReaderWithStats
// The returned account might be nil if it's not existent. // The returned account might be nil if it's not existent.
// //
// An error will be returned if the state is corrupted in the underlying reader. // An error will be returned if the state is corrupted in the underlying reader.
func (r *readerWithStats) Account(addr common.Address) (*types.StateAccount, error) { func (r *stateReaderWithStats) Account(addr common.Address) (*types.StateAccount, error) {
account, incache, err := r.stateReaderWithCache.account(addr) account, incache, err := r.stateReaderWithCache.account(addr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -659,7 +499,7 @@ func (r *readerWithStats) Account(addr common.Address) (*types.StateAccount, err
// existent. // existent.
// //
// An error will be returned if the state is corrupted in the underlying reader. // An error will be returned if the state is corrupted in the underlying reader.
func (r *readerWithStats) Storage(addr common.Address, slot common.Hash) (common.Hash, error) { func (r *stateReaderWithStats) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
value, incache, err := r.stateReaderWithCache.storage(addr, slot) value, incache, err := r.stateReaderWithCache.storage(addr, slot)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
@ -672,13 +512,51 @@ func (r *readerWithStats) Storage(addr common.Address, slot common.Hash) (common
return value, nil return value, nil
} }
// GetStats implements ReaderWithStats, returning the statistics of state reader. // GetStateStats implements StateReaderStater, returning the statistics of the
func (r *readerWithStats) GetStats() ReaderStats { // state reader.
return ReaderStats{ func (r *stateReaderWithStats) GetStateStats() StateReaderStats {
return StateReaderStats{
AccountCacheHit: r.accountCacheHit.Load(), AccountCacheHit: r.accountCacheHit.Load(),
AccountCacheMiss: r.accountCacheMiss.Load(), AccountCacheMiss: r.accountCacheMiss.Load(),
StorageCacheHit: r.storageCacheHit.Load(), StorageCacheHit: r.storageCacheHit.Load(),
StorageCacheMiss: r.storageCacheMiss.Load(), StorageCacheMiss: r.storageCacheMiss.Load(),
CodeStats: r.ContractCodeReaderWithStats.GetStats(), }
}
// reader aggregates a code reader and a state reader into a single object.
type reader struct {
ContractCodeReader
StateReader
}
// newReader constructs a reader with the supplied code reader and state reader.
func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
return &reader{
ContractCodeReader: codeReader,
StateReader: stateReader,
}
}
// GetCodeStats returns the statistics of code access.
func (r *reader) GetCodeStats() ContractCodeReaderStats {
if stater, ok := r.ContractCodeReader.(ContractCodeReaderStater); ok {
return stater.GetCodeStats()
}
return ContractCodeReaderStats{}
}
// GetStateStats returns the statistics of state access.
func (r *reader) GetStateStats() StateReaderStats {
if stater, ok := r.StateReader.(StateReaderStater); ok {
return stater.GetStateStats()
}
return StateReaderStats{}
}
// GetStats returns the aggregated statistics for both state and code access.
func (r *reader) GetStats() ReaderStats {
return ReaderStats{
CodeStats: r.GetCodeStats(),
StateStats: r.GetStateStats(),
} }
} }

View file

@ -0,0 +1,82 @@
// Copyright 2026 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package state
// ContractCodeReaderStats aggregates statistics for the contract code reader.
type ContractCodeReaderStats struct {
CacheHit int64 // Number of cache hits
CacheMiss int64 // Number of cache misses
CacheHitBytes int64 // Total bytes served from cache
CacheMissBytes int64 // Total bytes read on cache misses
}
// HitRate returns the cache hit rate in percentage.
func (s ContractCodeReaderStats) HitRate() float64 {
total := s.CacheHit + s.CacheMiss
if total == 0 {
return 0
}
return float64(s.CacheHit) / float64(total) * 100
}
// ContractCodeReaderStater wraps the method to retrieve the statistics of
// contract code reader.
type ContractCodeReaderStater interface {
GetCodeStats() ContractCodeReaderStats
}
// StateReaderStats aggregates statistics for the state reader.
type StateReaderStats struct {
AccountCacheHit int64 // Number of account cache hits
AccountCacheMiss int64 // Number of account cache misses
StorageCacheHit int64 // Number of storage cache hits
StorageCacheMiss int64 // Number of storage cache misses
}
// AccountCacheHitRate returns the cache hit rate of account requests in percentage.
func (s StateReaderStats) AccountCacheHitRate() float64 {
total := s.AccountCacheHit + s.AccountCacheMiss
if total == 0 {
return 0
}
return float64(s.AccountCacheHit) / float64(total) * 100
}
// StorageCacheHitRate returns the cache hit rate of storage requests in percentage.
func (s StateReaderStats) StorageCacheHitRate() float64 {
total := s.StorageCacheHit + s.StorageCacheMiss
if total == 0 {
return 0
}
return float64(s.StorageCacheHit) / float64(total) * 100
}
// StateReaderStater wraps the method to retrieve the statistics of state reader.
type StateReaderStater interface {
GetStateStats() StateReaderStats
}
// ReaderStats wraps the statistics of reader.
type ReaderStats struct {
CodeStats ContractCodeReaderStats
StateStats StateReaderStats
}
// ReaderStater defines the capability to retrieve aggregated statistics.
type ReaderStater interface {
GetStats() ReaderStats
}

View file

@ -195,6 +195,19 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
// have been handles via pendingStorage above. // have been handles via pendingStorage above.
// 2) we don't have new values, and can deliver empty response back // 2) we don't have new values, and can deliver empty response back
if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed { if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed {
// Invoke the reader regardless and discard the returned value.
// The returned value may not be empty, as it could belong to a
// self-destructed contract.
//
// The read operation is still essential for correctly building
// the block-level access list.
//
// TODO(rjl493456442) the reader interface can be extended with
// Touch, recording the read access without the actual disk load.
_, err := s.db.reader.Storage(s.address, key)
if err != nil {
s.db.setError(err)
}
s.originStorage[key] = common.Hash{} // track the empty slot as origin value s.originStorage[key] = common.Hash{} // track the empty slot as origin value
return common.Hash{} return common.Hash{}
} }
@ -551,10 +564,7 @@ func (s *stateObject) Code() []byte {
s.db.CodeLoadBytes += len(s.code) s.db.CodeLoadBytes += len(s.code)
}(time.Now()) }(time.Now())
code, err := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash())) code := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))
if err != nil {
s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
}
if len(code) == 0 { if len(code) == 0 {
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash())) s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
} }
@ -577,10 +587,7 @@ func (s *stateObject) CodeSize() int {
s.db.CodeReads += time.Since(start) s.db.CodeReads += time.Since(start)
}(time.Now()) }(time.Now())
size, err := s.db.reader.CodeSize(s.address, common.BytesToHash(s.CodeHash())) size := s.db.reader.CodeSize(s.address, common.BytesToHash(s.CodeHash()))
if err != nil {
s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
}
if size == 0 { if size == 0 {
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash())) s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
} }

View file

@ -28,7 +28,6 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
@ -148,8 +147,7 @@ type StateDB struct {
StorageReads time.Duration StorageReads time.Duration
StorageUpdates time.Duration StorageUpdates time.Duration
StorageCommits time.Duration StorageCommits time.Duration
SnapshotCommits time.Duration DatabaseCommits time.Duration
TrieDBCommits time.Duration
CodeReads time.Duration CodeReads time.Duration
AccountLoaded int // Number of accounts retrieved from the database during the state transition AccountLoaded int // Number of accounts retrieved from the database during the state transition
@ -1333,41 +1331,14 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorag
return nil, err return nil, err
} }
} }
// Commit dirty contract code if any exists start := time.Now()
if db := s.db.TrieDB().Disk(); db != nil && len(ret.codes) > 0 { if err := s.db.Commit(ret); err != nil {
batch := db.NewBatch() return nil, err
for _, code := range ret.codes {
rawdb.WriteCode(batch, code.hash, code.blob)
}
if err := batch.Write(); err != nil {
return nil, err
}
}
if !ret.empty() {
// If snapshotting is enabled, update the snapshot tree with this new version
if snap := s.db.Snapshot(); snap != nil && snap.Snapshot(ret.originRoot) != nil {
start := time.Now()
if err := snap.Update(ret.root, ret.originRoot, ret.accounts, ret.storages); err != nil {
log.Warn("Failed to update snapshot tree", "from", ret.originRoot, "to", ret.root, "err", err)
}
// Keep 128 diff layers in the memory, persistent layer is 129th.
// - head layer is paired with HEAD state
// - head-1 layer is paired with HEAD-1 state
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
if err := snap.Cap(ret.root, TriesInMemory); err != nil {
log.Warn("Failed to cap snapshot tree", "root", ret.root, "layers", TriesInMemory, "err", err)
}
s.SnapshotCommits += time.Since(start)
}
// If trie database is enabled, commit the state update as a new layer
if db := s.db.TrieDB(); db != nil {
start := time.Now()
if err := db.Update(ret.root, ret.originRoot, block, ret.nodes, ret.stateSet()); err != nil {
return nil, err
}
s.TrieDBCommits += time.Since(start)
}
} }
s.DatabaseCommits = time.Since(start)
// The reader update must be performed as the final step, otherwise,
// the new state would not be visible before db.commit.
s.reader, _ = s.db.Reader(s.originalRoot) s.reader, _ = s.db.Reader(s.originalRoot)
return ret, err return ret, err
} }

View file

@ -209,7 +209,7 @@ func (test *stateTest) run() bool {
if i != 0 { if i != 0 {
root = roots[len(roots)-1] root = roots[len(roots)-1]
} }
state, err := New(root, NewDatabase(tdb, snaps)) state, err := New(root, NewDatabase(tdb, nil).WithSnapshot(snaps))
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -1276,7 +1276,7 @@ func TestDeleteStorage(t *testing.T) {
disk = rawdb.NewMemoryDatabase() disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, nil) tdb = triedb.NewDatabase(disk, nil)
snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash) snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash)
db = NewDatabase(tdb, snaps) db = NewDatabase(tdb, nil).WithSnapshot(snaps)
state, _ = New(types.EmptyRootHash, db) state, _ = New(types.EmptyRootHash, db)
addr = common.HexToAddress("0x1") addr = common.HexToAddress("0x1")
) )
@ -1290,7 +1290,7 @@ func TestDeleteStorage(t *testing.T) {
} }
root, _ := state.Commit(0, true, false) root, _ := state.Commit(0, true, false)
// Init phase done, create two states, one with snap and one without // Init phase done, create two states, one with snap and one without
fastState, _ := New(root, NewDatabase(tdb, snaps)) fastState, _ := New(root, NewDatabase(tdb, nil).WithSnapshot(snaps))
slowState, _ := New(root, NewDatabase(tdb, nil)) slowState, _ := New(root, NewDatabase(tdb, nil))
obj := fastState.getOrNewStateObject(addr) obj := fastState.getOrNewStateObject(addr)

View file

@ -211,9 +211,9 @@ func (sc *stateUpdate) deriveCodeFields(reader ContractCodeReader) error {
cache := make(map[common.Hash]bool) cache := make(map[common.Hash]bool)
for addr, code := range sc.codes { for addr, code := range sc.codes {
if code.originHash != types.EmptyCodeHash { if code.originHash != types.EmptyCodeHash {
blob, err := reader.Code(addr, code.originHash) blob := reader.Code(addr, code.originHash)
if err != nil { if len(blob) == 0 {
return err return fmt.Errorf("original code of %x is empty", addr)
} }
code.originBlob = blob code.originBlob = blob
} }

View file

@ -222,8 +222,8 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool, s
codeResults = make([]trie.CodeSyncResult, len(codeElements)) codeResults = make([]trie.CodeSyncResult, len(codeElements))
) )
for i, element := range codeElements { for i, element := range codeElements {
data, err := cReader.Code(common.Address{}, element.code) data := cReader.Code(common.Address{}, element.code)
if err != nil || len(data) == 0 { if len(data) == 0 {
t.Fatalf("failed to retrieve contract bytecode for hash %x", element.code) t.Fatalf("failed to retrieve contract bytecode for hash %x", element.code)
} }
codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data} codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data}
@ -346,8 +346,8 @@ func testIterativeDelayedStateSync(t *testing.T, scheme string) {
if len(codeElements) > 0 { if len(codeElements) > 0 {
codeResults := make([]trie.CodeSyncResult, len(codeElements)/2+1) codeResults := make([]trie.CodeSyncResult, len(codeElements)/2+1)
for i, element := range codeElements[:len(codeResults)] { for i, element := range codeElements[:len(codeResults)] {
data, err := cReader.Code(common.Address{}, element.code) data := cReader.Code(common.Address{}, element.code)
if err != nil || len(data) == 0 { if len(data) == 0 {
t.Fatalf("failed to retrieve contract bytecode for %x", element.code) t.Fatalf("failed to retrieve contract bytecode for %x", element.code)
} }
codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data} codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data}
@ -452,8 +452,8 @@ func testIterativeRandomStateSync(t *testing.T, count int, scheme string) {
if len(codeQueue) > 0 { if len(codeQueue) > 0 {
results := make([]trie.CodeSyncResult, 0, len(codeQueue)) results := make([]trie.CodeSyncResult, 0, len(codeQueue))
for hash := range codeQueue { for hash := range codeQueue {
data, err := cReader.Code(common.Address{}, hash) data := cReader.Code(common.Address{}, hash)
if err != nil || len(data) == 0 { if len(data) == 0 {
t.Fatalf("failed to retrieve node data for %x", hash) t.Fatalf("failed to retrieve node data for %x", hash)
} }
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data}) results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
@ -551,8 +551,8 @@ func testIterativeRandomDelayedStateSync(t *testing.T, scheme string) {
for hash := range codeQueue { for hash := range codeQueue {
delete(codeQueue, hash) delete(codeQueue, hash)
data, err := cReader.Code(common.Address{}, hash) data := cReader.Code(common.Address{}, hash)
if err != nil || len(data) == 0 { if len(data) == 0 {
t.Fatalf("failed to retrieve node data for %x", hash) t.Fatalf("failed to retrieve node data for %x", hash)
} }
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data}) results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
@ -671,8 +671,8 @@ func testIncompleteStateSync(t *testing.T, scheme string) {
if len(codeQueue) > 0 { if len(codeQueue) > 0 {
results := make([]trie.CodeSyncResult, 0, len(codeQueue)) results := make([]trie.CodeSyncResult, 0, len(codeQueue))
for hash := range codeQueue { for hash := range codeQueue {
data, err := cReader.Code(common.Address{}, hash) data := cReader.Code(common.Address{}, hash)
if err != nil || len(data) == 0 { if len(data) == 0 {
t.Fatalf("failed to retrieve node data for %x", hash) t.Fatalf("failed to retrieve node data for %x", hash)
} }
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data}) results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})

View file

@ -107,7 +107,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
// We attempt to apply a transaction. The goal is not to execute // We attempt to apply a transaction. The goal is not to execute
// the transaction successfully, rather to warm up touched data slots. // the transaction successfully, rather to warm up touched data slots.
if _, err := ApplyMessage(evm, msg, new(GasPool).AddGas(block.GasLimit())); err != nil { if _, err := ApplyMessage(evm, msg, nil); err != nil {
fails.Add(1) fails.Add(1)
return nil // Ugh, something went horribly wrong, bail out return nil // Ugh, something went horribly wrong, bail out
} }

View file

@ -63,14 +63,12 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
var ( var (
config = p.chainConfig() config = p.chainConfig()
receipts types.Receipts receipts types.Receipts
usedGas = new(uint64)
header = block.Header() header = block.Header()
blockHash = block.Hash() blockHash = block.Hash()
blockNumber = block.Number() blockNumber = block.Number()
allLogs []*types.Log allLogs []*types.Log
gp = new(GasPool).AddGas(block.GasLimit()) gp = NewGasPool(block.GasLimit())
) )
var tracingStateDB = vm.StateDB(statedb) var tracingStateDB = vm.StateDB(statedb)
if hooks := cfg.Tracer; hooks != nil { if hooks := cfg.Tracer; hooks != nil {
tracingStateDB = state.NewHookedState(statedb, hooks) tracingStateDB = state.NewHookedState(statedb, hooks)
@ -107,13 +105,15 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
telemetry.StringAttribute("tx.hash", tx.Hash().Hex()), telemetry.StringAttribute("tx.hash", tx.Hash().Hex()),
telemetry.Int64Attribute("tx.index", int64(i)), telemetry.Int64Attribute("tx.index", int64(i)),
) )
receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, usedGas, evm)
spanEnd(&err) receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, evm)
if err != nil { if err != nil {
spanEnd(&err)
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
} }
receipts = append(receipts, receipt) receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...) allLogs = append(allLogs, receipt.Logs...)
spanEnd(nil)
} }
requests, err := postExecution(ctx, config, block, allLogs, evm) requests, err := postExecution(ctx, config, block, allLogs, evm)
if err != nil { if err != nil {
@ -127,7 +127,7 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
Receipts: receipts, Receipts: receipts,
Requests: requests, Requests: requests,
Logs: allLogs, Logs: allLogs,
GasUsed: *usedGas, GasUsed: gp.Used(),
}, nil }, nil
} }
@ -159,7 +159,7 @@ func postExecution(ctx context.Context, config *params.ChainConfig, block *types
// ApplyTransactionWithEVM attempts to apply a transaction to the given state database // ApplyTransactionWithEVM attempts to apply a transaction to the given state database
// and uses the input parameters for its environment similar to ApplyTransaction. However, // and uses the input parameters for its environment similar to ApplyTransaction. However,
// this method takes an already created EVM instance as input. // this method takes an already created EVM instance as input.
func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, err error) { func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, evm *vm.EVM) (receipt *types.Receipt, err error) {
if hooks := evm.Config.Tracer; hooks != nil { if hooks := evm.Config.Tracer; hooks != nil {
if hooks.OnTxStart != nil { if hooks.OnTxStart != nil {
hooks.OnTxStart(evm.GetVMContext(), tx, msg.From) hooks.OnTxStart(evm.GetVMContext(), tx, msg.From)
@ -180,27 +180,31 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
} else { } else {
root = statedb.IntermediateRoot(evm.ChainConfig().IsEIP158(blockNumber)).Bytes() root = statedb.IntermediateRoot(evm.ChainConfig().IsEIP158(blockNumber)).Bytes()
} }
*usedGas += result.UsedGas
// Merge the tx-local access event into the "block-local" one, in order to collect // Merge the tx-local access event into the "block-local" one, in order to collect
// all values, so that the witness can be built. // all values, so that the witness can be built.
if statedb.Database().TrieDB().IsVerkle() { if statedb.Database().TrieDB().IsVerkle() {
statedb.AccessEvents().Merge(evm.AccessEvents) statedb.AccessEvents().Merge(evm.AccessEvents)
} }
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, *usedGas, root), nil return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, gp.CumulativeUsed(), root), nil
} }
// MakeReceipt generates the receipt object for a transaction given its execution result. // MakeReceipt generates the receipt object for a transaction given its execution result.
func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt { func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, cumulativeGas uint64, root []byte) *types.Receipt {
// Create a new receipt for the transaction, storing the intermediate root and gas used // Create a new receipt for the transaction, storing the intermediate root
// by the tx. // and gas used by the tx.
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: usedGas} //
// The cumulative gas used equals the sum of gasUsed across all preceding
// txs with refunded gas deducted.
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: cumulativeGas}
if result.Failed() { if result.Failed() {
receipt.Status = types.ReceiptStatusFailed receipt.Status = types.ReceiptStatusFailed
} else { } else {
receipt.Status = types.ReceiptStatusSuccessful receipt.Status = types.ReceiptStatusSuccessful
} }
receipt.TxHash = tx.Hash() receipt.TxHash = tx.Hash()
// GasUsed = max(tx_gas_used - gas_refund, calldata_floor_gas_cost), unchanged
// in the Amsterdam fork.
receipt.GasUsed = result.UsedGas receipt.GasUsed = result.UsedGas
if tx.Type() == types.BlobTxType { if tx.Type() == types.BlobTxType {
@ -224,15 +228,15 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b
// ApplyTransaction attempts to apply a transaction to the given state database // ApplyTransaction attempts to apply a transaction to the given state database
// and uses the input parameters for its environment. It returns the receipt // and uses the input parameters for its environment. It returns the receipt
// for the transaction, gas used and an error if the transaction failed, // for the transaction and an error if the transaction failed,
// indicating the block was invalid. // indicating the block was invalid.
func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64) (*types.Receipt, error) { func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction) (*types.Receipt, error) {
msg, err := TransactionToMessage(tx, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee) msg, err := TransactionToMessage(tx, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Create a new context to be used in the EVM environment // Create a new context to be used in the EVM environment
return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, usedGas, evm) return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, evm)
} }
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root // ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root

View file

@ -422,6 +422,9 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
beaconRoot := common.HexToHash("0xbeac00") beaconRoot := common.HexToHash("0xbeac00")
header.ParentBeaconRoot = &beaconRoot header.ParentBeaconRoot = &beaconRoot
} }
if config.IsAmsterdam(header.Number, header.Time) {
header.SlotNumber = new(uint64)
}
// Assemble and return the final block for sealing // Assemble and return the final block for sealing
body := &types.Body{Transactions: txs} body := &types.Body{Transactions: txs}
if config.IsShanghai(header.Number, header.Time) { if config.IsShanghai(header.Number, header.Time) {

View file

@ -34,7 +34,7 @@ import (
// ExecutionResult includes all output after executing given evm // ExecutionResult includes all output after executing given evm
// message no matter the execution itself is successful or not. // message no matter the execution itself is successful or not.
type ExecutionResult struct { type ExecutionResult struct {
UsedGas uint64 // Total used gas, not including the refunded gas UsedGas uint64 // Total used gas, refunded gas is deducted
MaxUsedGas uint64 // Maximum gas consumed during execution, excluding gas refunds. MaxUsedGas uint64 // Maximum gas consumed during execution, excluding gas refunds.
Err error // Any error encountered during the execution(listed in core/vm/errors.go) Err error // Any error encountered during the execution(listed in core/vm/errors.go)
ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
@ -177,9 +177,9 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
msg := &Message{ msg := &Message{
Nonce: tx.Nonce(), Nonce: tx.Nonce(),
GasLimit: tx.Gas(), GasLimit: tx.Gas(),
GasPrice: new(big.Int).Set(tx.GasPrice()), GasPrice: tx.GasPrice(),
GasFeeCap: new(big.Int).Set(tx.GasFeeCap()), GasFeeCap: tx.GasFeeCap(),
GasTipCap: new(big.Int).Set(tx.GasTipCap()), GasTipCap: tx.GasTipCap(),
To: tx.To(), To: tx.To(),
Value: tx.Value(), Value: tx.Value(),
Data: tx.Data(), Data: tx.Data(),
@ -210,6 +210,11 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
// indicates a core error meaning that the message would always fail for that particular // indicates a core error meaning that the message would always fail for that particular
// state and would never be accepted within a block. // state and would never be accepted within a block.
func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) { func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) {
// Do not panic if the gas pool is nil. This is allowed when executing
// a single message via RPC invocation.
if gp == nil {
gp = NewGasPool(msg.GasLimit)
}
evm.SetTxContext(NewEVMTxContext(msg)) evm.SetTxContext(NewEVMTxContext(msg))
return newStateTransition(evm, msg, gp).execute() return newStateTransition(evm, msg, gp).execute()
} }
@ -300,8 +305,8 @@ func (st *stateTransition) buyGas() error {
st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance) st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance)
} }
st.gasRemaining = st.msg.GasLimit st.gasRemaining = st.msg.GasLimit
st.initialGas = st.msg.GasLimit st.initialGas = st.msg.GasLimit
mgvalU256, _ := uint256.FromBig(mgval) mgvalU256, _ := uint256.FromBig(mgval)
st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy) st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
return nil return nil
@ -483,8 +488,10 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
} }
// Check whether the init code size has been exceeded. // Check whether the init code size has been exceeded.
if rules.IsShanghai && contractCreation && len(msg.Data) > params.MaxInitCodeSize { if contractCreation {
return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize) if err := vm.CheckMaxInitCodeSize(&rules, uint64(len(msg.Data))); err != nil {
return nil, err
}
} }
// Execute the preparatory steps for state transition which includes: // Execute the preparatory steps for state transition which includes:
@ -542,8 +549,20 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
peakGasUsed = floorDataGas peakGasUsed = floorDataGas
} }
} }
// Return gas to the user
st.returnGas() st.returnGas()
// Return gas to the gas pool
if rules.IsAmsterdam {
// Refund is excluded for returning
err = st.gp.ReturnGas(st.initialGas-peakGasUsed, st.gasUsed())
} else {
// Refund is included for returning
err = st.gp.ReturnGas(st.gasRemaining, st.gasUsed())
}
if err != nil {
return nil, err
}
effectiveTip := msg.GasPrice effectiveTip := msg.GasPrice
if rules.IsLondon { if rules.IsLondon {
effectiveTip = new(big.Int).Sub(msg.GasPrice, st.evm.Context.BaseFee) effectiveTip = new(big.Int).Sub(msg.GasPrice, st.evm.Context.BaseFee)
@ -564,7 +583,6 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64) st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64)
} }
} }
return &ExecutionResult{ return &ExecutionResult{
UsedGas: st.gasUsed(), UsedGas: st.gasUsed(),
MaxUsedGas: peakGasUsed, MaxUsedGas: peakGasUsed,
@ -660,10 +678,6 @@ func (st *stateTransition) returnGas() {
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 { if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned) st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned)
} }
// Also return remaining gas to the block gas counter so it is
// available for the next transaction.
st.gp.AddGas(st.gasRemaining)
} }
// gasUsed returns the amount of gas used up by the state transition. // gasUsed returns the amount of gas used up by the state transition.

View file

@ -53,7 +53,7 @@ func ExecuteStateless(ctx context.Context, config *params.ChainConfig, vmconfig
} }
// Create and populate the state database to serve as the stateless backend // Create and populate the state database to serve as the stateless backend
memdb := witness.MakeHashDB() memdb := witness.MakeHashDB()
db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), nil)) db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), state.NewCodeDB(memdb)))
if err != nil { if err != nil {
return common.Hash{}, common.Hash{}, err return common.Hash{}, common.Hash{}, err
} }

View file

@ -40,8 +40,8 @@ func (w *Witness) ToExtWitness() *ExtWitness {
return ext return ext
} }
// fromExtWitness converts the consensus witness format into our internal one. // FromExtWitness converts the consensus witness format into our internal one.
func (w *Witness) fromExtWitness(ext *ExtWitness) error { func (w *Witness) FromExtWitness(ext *ExtWitness) error {
w.Headers = ext.Headers w.Headers = ext.Headers
w.Codes = make(map[string]struct{}, len(ext.Codes)) w.Codes = make(map[string]struct{}, len(ext.Codes))
@ -66,7 +66,7 @@ func (w *Witness) DecodeRLP(s *rlp.Stream) error {
if err := s.Decode(&ext); err != nil { if err := s.Decode(&ext); err != nil {
return err return err
} }
return w.fromExtWitness(&ext) return w.FromExtWitness(&ext)
} }
// ExtWitness is a witness RLP encoding for transferring across clients. // ExtWitness is a witness RLP encoding for transferring across clients.

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/trie"
) )
var accountTrieLeavesAtDepth [16]*metrics.Counter var accountTrieLeavesAtDepth [16]*metrics.Counter
@ -41,59 +42,68 @@ func init() {
// WitnessStats aggregates statistics for account and storage trie accesses. // WitnessStats aggregates statistics for account and storage trie accesses.
type WitnessStats struct { type WitnessStats struct {
accountTrieLeaves [16]int64 accountTrie *trie.LevelStats
storageTrieLeaves [16]int64 storageTrie *trie.LevelStats
} }
// NewWitnessStats creates a new WitnessStats collector. // NewWitnessStats creates a new WitnessStats collector.
func NewWitnessStats() *WitnessStats { func NewWitnessStats() *WitnessStats {
return &WitnessStats{} return &WitnessStats{
accountTrie: trie.NewLevelStats(),
storageTrie: trie.NewLevelStats(),
}
}
func (s *WitnessStats) init() {
if s.accountTrie == nil {
s.accountTrie = trie.NewLevelStats()
}
if s.storageTrie == nil {
s.storageTrie = trie.NewLevelStats()
}
} }
// Add records trie access depths from the given node paths. // Add records trie access depths from the given node paths.
// If `owner` is the zero hash, accesses are attributed to the account trie; // If `owner` is the zero hash, accesses are attributed to the account trie;
// otherwise, they are attributed to the storage trie of that account. // otherwise, they are attributed to the storage trie of that account.
func (s *WitnessStats) Add(nodes map[string][]byte, owner common.Hash) { func (s *WitnessStats) Add(nodes map[string][]byte, owner common.Hash) {
// Extract paths from the nodes map s.init()
// Extract paths from the nodes map.
paths := slices.Collect(maps.Keys(nodes)) paths := slices.Collect(maps.Keys(nodes))
sort.Strings(paths) sort.Strings(paths)
ownerStat := s.accountTrie
if owner != (common.Hash{}) {
ownerStat = s.storageTrie
}
for i, path := range paths { for i, path := range paths {
// If current path is a prefix of the next path, it's not a leaf. // If current path is a prefix of the next path, it's not a leaf.
// The last path is always a leaf. // The last path is always a leaf.
if i == len(paths)-1 || !strings.HasPrefix(paths[i+1], paths[i]) { if i == len(paths)-1 || !strings.HasPrefix(paths[i+1], paths[i]) {
depth := len(path) ownerStat.AddLeaf(len(path))
if owner == (common.Hash{}) {
if depth >= len(s.accountTrieLeaves) {
depth = len(s.accountTrieLeaves) - 1
}
s.accountTrieLeaves[depth] += 1
} else {
if depth >= len(s.storageTrieLeaves) {
depth = len(s.storageTrieLeaves) - 1
}
s.storageTrieLeaves[depth] += 1
}
} }
} }
} }
// ReportMetrics reports the collected statistics to the global metrics registry. // ReportMetrics reports the collected statistics to the global metrics registry.
func (s *WitnessStats) ReportMetrics(blockNumber uint64) { func (s *WitnessStats) ReportMetrics(blockNumber uint64) {
// Encode the metrics as JSON for easier consumption s.init()
accountLeavesJson, _ := json.Marshal(s.accountTrieLeaves)
storageLeavesJson, _ := json.Marshal(s.storageTrieLeaves)
// Log account trie depth statistics accountTrieLeaves := s.accountTrie.LeafDepths()
log.Info("Account trie depth stats", storageTrieLeaves := s.storageTrie.LeafDepths()
"block", blockNumber,
"leavesAtDepth", string(accountLeavesJson))
log.Info("Storage trie depth stats",
"block", blockNumber,
"leavesAtDepth", string(storageLeavesJson))
for i := 0; i < 16; i++ { // Encode the metrics as JSON for easier consumption.
accountTrieLeavesAtDepth[i].Inc(s.accountTrieLeaves[i]) accountLeavesJSON, _ := json.Marshal(accountTrieLeaves)
storageTrieLeavesAtDepth[i].Inc(s.storageTrieLeaves[i]) storageLeavesJSON, _ := json.Marshal(storageTrieLeaves)
// Log account trie depth statistics.
log.Info("Account trie depth stats", "block", blockNumber, "leavesAtDepth", string(accountLeavesJSON))
log.Info("Storage trie depth stats", "block", blockNumber, "leavesAtDepth", string(storageLeavesJSON))
for i := 0; i < len(accountTrieLeavesAtDepth); i++ {
accountTrieLeavesAtDepth[i].Inc(accountTrieLeaves[i])
storageTrieLeavesAtDepth[i].Inc(storageTrieLeaves[i])
} }
} }

View file

@ -17,18 +17,27 @@
package stateless package stateless
import ( import (
"strings"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
func expectedLeaves(counts map[int]int64) [16]int64 {
var leaves [16]int64
for depth, count := range counts {
leaves[depth] = count
}
return leaves
}
func TestWitnessStatsAdd(t *testing.T) { func TestWitnessStatsAdd(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
nodes map[string][]byte nodes map[string][]byte
owner common.Hash owner common.Hash
expectedAccountLeaves map[int64]int64 expectedAccountLeaves map[int]int64
expectedStorageLeaves map[int64]int64 expectedStorageLeaves map[int]int64
}{ }{
{ {
name: "empty nodes", name: "empty nodes",
@ -41,7 +50,7 @@ func TestWitnessStatsAdd(t *testing.T) {
"": []byte("data"), "": []byte("data"),
}, },
owner: common.Hash{}, owner: common.Hash{},
expectedAccountLeaves: map[int64]int64{0: 1}, expectedAccountLeaves: map[int]int64{0: 1},
}, },
{ {
name: "single account trie leaf", name: "single account trie leaf",
@ -49,7 +58,7 @@ func TestWitnessStatsAdd(t *testing.T) {
"abc": []byte("data"), "abc": []byte("data"),
}, },
owner: common.Hash{}, owner: common.Hash{},
expectedAccountLeaves: map[int64]int64{3: 1}, expectedAccountLeaves: map[int]int64{3: 1},
}, },
{ {
name: "account trie with internal nodes", name: "account trie with internal nodes",
@ -59,7 +68,7 @@ func TestWitnessStatsAdd(t *testing.T) {
"abc": []byte("data3"), "abc": []byte("data3"),
}, },
owner: common.Hash{}, owner: common.Hash{},
expectedAccountLeaves: map[int64]int64{3: 1}, // Only "abc" is a leaf expectedAccountLeaves: map[int]int64{3: 1}, // Only "abc" is a leaf
}, },
{ {
name: "multiple account trie branches", name: "multiple account trie branches",
@ -72,7 +81,7 @@ func TestWitnessStatsAdd(t *testing.T) {
"bcd": []byte("data6"), "bcd": []byte("data6"),
}, },
owner: common.Hash{}, owner: common.Hash{},
expectedAccountLeaves: map[int64]int64{3: 2}, // "abc" (3) + "bcd" (3) expectedAccountLeaves: map[int]int64{3: 2}, // "abc" (3) + "bcd" (3)
}, },
{ {
name: "siblings are all leaves", name: "siblings are all leaves",
@ -82,7 +91,7 @@ func TestWitnessStatsAdd(t *testing.T) {
"ac": []byte("data3"), "ac": []byte("data3"),
}, },
owner: common.Hash{}, owner: common.Hash{},
expectedAccountLeaves: map[int64]int64{2: 3}, expectedAccountLeaves: map[int]int64{2: 3},
}, },
{ {
name: "storage trie leaves", name: "storage trie leaves",
@ -93,7 +102,7 @@ func TestWitnessStatsAdd(t *testing.T) {
"124": []byte("data4"), "124": []byte("data4"),
}, },
owner: common.HexToHash("0x1234"), owner: common.HexToHash("0x1234"),
expectedStorageLeaves: map[int64]int64{3: 2}, // "123" (3) + "124" (3) expectedStorageLeaves: map[int]int64{3: 2}, // "123" (3) + "124" (3)
}, },
{ {
name: "complex trie structure", name: "complex trie structure",
@ -109,7 +118,7 @@ func TestWitnessStatsAdd(t *testing.T) {
"3": []byte("data9"), "3": []byte("data9"),
}, },
owner: common.Hash{}, owner: common.Hash{},
expectedAccountLeaves: map[int64]int64{1: 1, 3: 4}, // "123"(3) + "124"(3) + "234"(3) + "235"(3) + "3"(1) expectedAccountLeaves: map[int]int64{1: 1, 3: 4}, // "123"(3) + "124"(3) + "234"(3) + "235"(3) + "3"(1)
}, },
} }
@ -118,32 +127,59 @@ func TestWitnessStatsAdd(t *testing.T) {
stats := NewWitnessStats() stats := NewWitnessStats()
stats.Add(tt.nodes, tt.owner) stats.Add(tt.nodes, tt.owner)
var expectedAccountTrieLeaves [16]int64 if got, want := stats.accountTrie.LeafDepths(), expectedLeaves(tt.expectedAccountLeaves); got != want {
for depth, count := range tt.expectedAccountLeaves { t.Errorf("account trie leaves = %v, want %v", got, want)
expectedAccountTrieLeaves[depth] = count
} }
var expectedStorageTrieLeaves [16]int64 if got, want := stats.storageTrie.LeafDepths(), expectedLeaves(tt.expectedStorageLeaves); got != want {
for depth, count := range tt.expectedStorageLeaves { t.Errorf("storage trie leaves = %v, want %v", got, want)
expectedStorageTrieLeaves[depth] = count
}
// Check account trie depth
if stats.accountTrieLeaves != expectedAccountTrieLeaves {
t.Errorf("Account trie total depth = %v, want %v", stats.accountTrieLeaves, expectedAccountTrieLeaves)
}
// Check storage trie depth
if stats.storageTrieLeaves != expectedStorageTrieLeaves {
t.Errorf("Storage trie total depth = %v, want %v", stats.storageTrieLeaves, expectedStorageTrieLeaves)
} }
}) })
} }
} }
func TestWitnessStatsStorageTrieAggregation(t *testing.T) {
stats := NewWitnessStats()
ownerA := common.HexToHash("0xa")
ownerB := common.HexToHash("0xb")
stats.Add(map[string][]byte{
"a": []byte("data1"),
"ab": []byte("data2"),
"abc": []byte("data3"),
}, ownerA)
stats.Add(map[string][]byte{
"xy": []byte("data4"),
}, ownerA)
stats.Add(map[string][]byte{
"1": []byte("data5"),
"12": []byte("data6"),
"123": []byte("data7"),
"124": []byte("data8"),
}, ownerB)
if got, want := stats.storageTrie.LeafDepths(), expectedLeaves(map[int]int64{2: 1, 3: 3}); got != want {
t.Errorf("storage leaves = %v, want %v", got, want)
}
if got, want := stats.accountTrie.LeafDepths(), expectedLeaves(nil); got != want {
t.Errorf("account leaves = %v, want %v", got, want)
}
}
func TestWitnessStatsPanicsOnDeepLeaf(t *testing.T) {
stats := NewWitnessStats()
defer func() {
if r := recover(); r == nil {
t.Fatal("expected panic for depth >= 16")
}
}()
stats.Add(map[string][]byte{strings.Repeat("a", 16): []byte("data")}, common.Hash{})
}
func TestWitnessStatsMinMax(t *testing.T) { func TestWitnessStatsMinMax(t *testing.T) {
stats := NewWitnessStats() stats := NewWitnessStats()
// Add some account trie nodes with varying depths // Add some account trie nodes with varying depths.
stats.Add(map[string][]byte{ stats.Add(map[string][]byte{
"a": []byte("data1"), "a": []byte("data1"),
"ab": []byte("data2"), "ab": []byte("data2"),
@ -152,21 +188,21 @@ func TestWitnessStatsMinMax(t *testing.T) {
"abcde": []byte("data5"), "abcde": []byte("data5"),
}, common.Hash{}) }, common.Hash{})
// Only "abcde" is a leaf (depth 5) // Only "abcde" is a leaf (depth 5).
for i, v := range stats.accountTrieLeaves { for i, v := range stats.accountTrie.LeafDepths() {
if v != 0 && i != 5 { if v != 0 && i != 5 {
t.Errorf("leaf found at invalid depth %d", i) t.Errorf("leaf found at invalid depth %d", i)
} }
} }
// Add more leaves with different depths // Add more leaves with different depths.
stats.Add(map[string][]byte{ stats.Add(map[string][]byte{
"x": []byte("data6"), "x": []byte("data6"),
"yz": []byte("data7"), "yz": []byte("data7"),
}, common.Hash{}) }, common.Hash{})
// Now we have leaves at depths 1, 2, and 5 // Now we have leaves at depths 1, 2, and 5.
for i, v := range stats.accountTrieLeaves { for i, v := range stats.accountTrie.LeafDepths() {
if v != 0 && (i != 5 && i != 2 && i != 1) { if v != 0 && (i != 5 && i != 2 && i != 1) {
t.Errorf("leaf found at invalid depth %d", i) t.Errorf("leaf found at invalid depth %d", i)
} }
@ -176,7 +212,7 @@ func TestWitnessStatsMinMax(t *testing.T) {
func TestWitnessStatsAverage(t *testing.T) { func TestWitnessStatsAverage(t *testing.T) {
stats := NewWitnessStats() stats := NewWitnessStats()
// Add nodes that will create leaves at depths 2, 3, and 4 // Add nodes that will create leaves at depths 2, 3, and 4.
stats.Add(map[string][]byte{ stats.Add(map[string][]byte{
"aa": []byte("data1"), "aa": []byte("data1"),
"bb": []byte("data2"), "bb": []byte("data2"),
@ -184,22 +220,22 @@ func TestWitnessStatsAverage(t *testing.T) {
"dddd": []byte("data4"), "dddd": []byte("data4"),
}, common.Hash{}) }, common.Hash{})
// All are leaves: 2 + 2 + 3 + 4 = 11 total, 4 samples // All are leaves: 2 + 2 + 3 + 4 = 11 total, 4 samples.
expectedAvg := int64(11) / int64(4) expectedAvg := int64(11) / int64(4)
var actualAvg, totalSamples int64 var actualAvg, totalSamples int64
for i, c := range stats.accountTrieLeaves { for i, c := range stats.accountTrie.LeafDepths() {
actualAvg += c * int64(i) actualAvg += c * int64(i)
totalSamples += c totalSamples += c
} }
actualAvg = actualAvg / totalSamples actualAvg = actualAvg / totalSamples
if actualAvg != expectedAvg { if actualAvg != expectedAvg {
t.Errorf("Account trie average depth = %d, want %d", actualAvg, expectedAvg) t.Errorf("account trie average depth = %d, want %d", actualAvg, expectedAvg)
} }
} }
func BenchmarkWitnessStatsAdd(b *testing.B) { func BenchmarkWitnessStatsAdd(b *testing.B) {
// Create a realistic trie node structure // Create a realistic trie node structure.
nodes := make(map[string][]byte) nodes := make(map[string][]byte)
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
base := string(rune('a' + i%26)) base := string(rune('a' + i%26))

View file

@ -358,7 +358,7 @@ const (
// this generates an increase in gas. There is at most one of such gas change per transaction. // this generates an increase in gas. There is at most one of such gas change per transaction.
GasChangeTxRefunds GasChangeReason = 3 GasChangeTxRefunds GasChangeReason = 3
// GasChangeTxLeftOverReturned is the amount of gas left over at the end of transaction's execution that will be returned // GasChangeTxLeftOverReturned is the amount of gas left over at the end of transaction's execution that will be returned
// to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas // to the account. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas
// left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller. // left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller.
// There is at most one of such gas change per transaction. // There is at most one of such gas change per transaction.
GasChangeTxLeftOverReturned GasChangeReason = 4 GasChangeTxLeftOverReturned GasChangeReason = 4

View file

@ -155,10 +155,18 @@ func (j *journal) OnBalanceChange(addr common.Address, prev, new *big.Int, reaso
} }
func (j *journal) OnNonceChangeV2(addr common.Address, prev, new uint64, reason NonceChangeReason) { func (j *journal) OnNonceChangeV2(addr common.Address, prev, new uint64, reason NonceChangeReason) {
// When a contract is created, the nonce of the creator is incremented. j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new})
// This change is not reverted when the creation fails. if reason == NonceChangeContractCreator {
if reason != NonceChangeContractCreator { // When a contract is created via CREATE/CREATE2, the creator's nonce is
j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new}) // incremented. The EVM does not revert this when the CREATE frame itself
// fails (the nonce change happens before the EVM snapshot). However, if
// a parent frame reverts, the nonce must be reverted along with everything
// else.
//
// To achieve this, advance the current frame's revision point past this
// entry. The CREATE frame's revert won't touch it (it's below the revision),
// but a parent frame's revert will (it's above the parent's revision).
j.revisions[len(j.revisions)-1] = len(j.entries)
} }
if j.hooks.OnNonceChangeV2 != nil { if j.hooks.OnNonceChangeV2 != nil {
j.hooks.OnNonceChangeV2(addr, prev, new, reason) j.hooks.OnNonceChangeV2(addr, prev, new, reason)

View file

@ -219,6 +219,42 @@ func TestNonceIncOnCreate(t *testing.T) {
} }
} }
// TestNonceIncOnCreateParentReverts checks that the creator's nonce increment
// from CREATE survives the CREATE frame's own revert but is properly reverted
// when the parent call frame reverts.
func TestNonceIncOnCreateParentReverts(t *testing.T) {
const opCREATE = 0xf0
tr := &testTracer{t: t}
wr, err := WrapWithJournal(&Hooks{OnNonceChange: tr.OnNonceChange})
if err != nil {
t.Fatalf("failed to wrap test tracer: %v", err)
}
addr := common.HexToAddress("0x1234")
{
// Parent call frame
wr.OnEnter(0, 0, addr, addr, nil, 1000, big.NewInt(0))
{
// CREATE frame — creator nonce incremented, then CREATE reverts
wr.OnEnter(1, opCREATE, addr, addr, nil, 1000, big.NewInt(0))
wr.OnNonceChangeV2(addr, 0, 1, NonceChangeContractCreator)
wr.OnExit(1, nil, 100, errors.New("revert"), true)
}
// After CREATE reverts, nonce should still be 1
if tr.nonce != 1 {
t.Fatalf("nonce after CREATE revert: got %v, want 1", tr.nonce)
}
// Parent frame also reverts
wr.OnExit(0, nil, 150, errors.New("revert"), true)
}
// After parent reverts, nonce should be back to 0
if tr.nonce != 0 {
t.Fatalf("nonce after parent revert: got %v, want 0", tr.nonce)
}
}
func TestOnNonceChangeV2(t *testing.T) { func TestOnNonceChangeV2(t *testing.T) {
tr := &testTracer{t: t} tr := &testTracer{t: t}
wr, err := WrapWithJournal(&Hooks{OnNonceChangeV2: tr.OnNonceChangeV2}) wr, err := WrapWithJournal(&Hooks{OnNonceChangeV2: tr.OnNonceChangeV2})

View file

@ -104,6 +104,16 @@ const (
// maxGappedTxs is the maximum number of gapped transactions kept overall. // maxGappedTxs is the maximum number of gapped transactions kept overall.
// This is a safety limit to avoid DoS vectors. // This is a safety limit to avoid DoS vectors.
maxGapped = 128 maxGapped = 128
// notifyThreshold is the eviction priority threshold above which a transaction
// is considered close enough to being includable to be announced to peers.
// Setting this to zero will disable announcements for anyting not immediately
// includable. Setting it to -1 allows transactions that are close to being
// includable, maybe already in the next block if fees go down, to be announced.
// Note, this threshold is in the abstract eviction priority space, so its
// meaning depends on the current basefee/blobfee and the transaction's fees.
announceThreshold = -1
) )
// blobTxMeta is the minimal subset of types.BlobTx necessary to validate and // blobTxMeta is the minimal subset of types.BlobTx necessary to validate and
@ -115,6 +125,8 @@ type blobTxMeta struct {
vhashes []common.Hash // Blob versioned hashes to maintain the lookup table vhashes []common.Hash // Blob versioned hashes to maintain the lookup table
version byte // Blob transaction version to determine proof type version byte // Blob transaction version to determine proof type
announced bool // Whether the tx has been announced to listeners
id uint64 // Storage ID in the pool's persistent store id uint64 // Storage ID in the pool's persistent store
storageSize uint32 // Byte size in the pool's persistent store storageSize uint32 // Byte size in the pool's persistent store
size uint64 // RLP-encoded size of transaction including the attached blob size uint64 // RLP-encoded size of transaction including the attached blob
@ -159,7 +171,7 @@ func newBlobTxMeta(id uint64, size uint64, storageSize uint32, tx *types.Transac
blobGas: tx.BlobGas(), blobGas: tx.BlobGas(),
} }
meta.basefeeJumps = dynamicFeeJumps(meta.execFeeCap) meta.basefeeJumps = dynamicFeeJumps(meta.execFeeCap)
meta.blobfeeJumps = dynamicFeeJumps(meta.blobFeeCap) meta.blobfeeJumps = dynamicBlobFeeJumps(meta.blobFeeCap)
return meta return meta
} }
@ -210,6 +222,14 @@ func newBlobTxMeta(id uint64, size uint64, storageSize uint32, tx *types.Transac
// via a normal transaction. It should nonetheless be high enough to support // via a normal transaction. It should nonetheless be high enough to support
// resurrecting reorged transactions. Perhaps 4-16. // resurrecting reorged transactions. Perhaps 4-16.
// //
// - It is not the role of the blobpool to serve as a storage for limit orders
// below market: blob transactions with fee caps way below base fee or blob fee.
// Therefore, the propagation of blob transactions that are far from being
// includable is suppressed. The pool will only announce blob transactions that
// are close to being includable (based on the current fees and the transaction's
// fee caps), and will delay the announcement of blob transactions that are far
// from being includable until base fee and/or blob fee is reduced.
//
// - Local txs are meaningless. Mining pools historically used local transactions // - Local txs are meaningless. Mining pools historically used local transactions
// for payouts or for backdoor deals. With 1559 in place, the basefee usually // for payouts or for backdoor deals. With 1559 in place, the basefee usually
// dominates the final price, so 0 or non-0 tip doesn't change much. Blob txs // dominates the final price, so 0 or non-0 tip doesn't change much. Blob txs
@ -281,47 +301,54 @@ func newBlobTxMeta(id uint64, size uint64, storageSize uint32, tx *types.Transac
// solve after every block. // solve after every block.
// //
// - The first observation is that comparing 1559 base fees or 4844 blob fees // - The first observation is that comparing 1559 base fees or 4844 blob fees
// needs to happen in the context of their dynamism. Since these fees jump // needs to happen in the context of their dynamism. Since base fees are
// up or down in ~1.125 multipliers (at max) across blocks, comparing fees // adjusted continuously and fluctuate, and we want to optimize for effective
// in two transactions should be based on log1.125(fee) to eliminate noise. // miner fees, it is better to disregard small base fee cap differences.
// Instead of considering the exact fee cap values, we should group
// transactions into buckets based on fee cap values, allowing us to use
// the miner tip meaningfully as a splitter inside a bucket.
// //
// - The second observation is that the basefee and blobfee move independently, // To create these buckets, rather than looking at the absolute fee
// so there's no way to split mixed txs on their own (A has higher base fee, // differences, the useful metric is the max time it can take to exceed the
// B has higher blob fee). Rather than look at the absolute fees, the useful // transaction's fee caps. Base fee changes are multiplicative, so we use a
// metric is the max time it can take to exceed the transaction's fee caps. // logarithmic scale. Fees jumps up or down in ~1.125 multipliers at max
// across blocks, so we use log1.125(fee) and rounding to eliminate noise.
// Specifically, we're interested in the number of jumps needed to go from // Specifically, we're interested in the number of jumps needed to go from
// the current fee to the transaction's cap: // the current fee to the transaction's cap:
// //
// jumps = log1.125(txfee) - log1.125(basefee) // jumps = floor(log1.125(txfee) - log1.125(basefee))
// //
// - The third observation is that the base fee tends to hover around rather // For blob fees, EIP-7892 changed the ratio of target to max blobs, and
// than swing wildly. The number of jumps needed from the current fee starts // with that also the maximum blob fee decrease in a slot from 1.125 to
// to get less relevant the higher it is. To remove the noise here too, the // approx 1.17. therefore, we use:
// pool will use log(jumps) as the delta for comparing transactions.
// //
// delta = sign(jumps) * log(abs(jumps)) // blobfeeJumps = floor(log1.17(txBlobfee) - log1.17(blobfee))
// //
// - To establish a total order, we need to reduce the dimensionality of the // - The second observation is that when ranking executable blob txs, it
// does not make sense to grant a later eviction priority to txs with high
// fee caps since it could enable pool wars. As such, any positive priority
// will be grouped together.
//
// priority = min(jumps, 0)
//
// - The third observation is that the basefee and blobfee move independently,
// so there's no way to split mixed txs on their own (A has higher base fee,
// B has higher blob fee).
//
// To establish a total order, we need to reduce the dimensionality of the
// two base fees (log jumps) to a single value. The interesting aspect from // two base fees (log jumps) to a single value. The interesting aspect from
// the pool's perspective is how fast will a tx get executable (fees going // the pool's perspective is how fast will a tx get executable (fees going
// down, crossing the smaller negative jump counter) or non-executable (fees // down, crossing the smaller negative jump counter) or non-executable (fees
// going up, crossing the smaller positive jump counter). As such, the pool // going up, crossing the smaller positive jump counter). As such, the pool
// cares only about the min of the two delta values for eviction priority. // cares only about the min of the two delta values for eviction priority.
// //
// priority = min(deltaBasefee, deltaBlobfee) // priority = min(deltaBasefee, deltaBlobfee, 0)
// //
// - The above very aggressive dimensionality and noise reduction should result // - The above very aggressive dimensionality and noise reduction should result
// in transaction being grouped into a small number of buckets, the further // in transaction being grouped into a small number of buckets, the further
// the fees the larger the buckets. This is good because it allows us to use // the fees the larger the buckets. This is good because it allows us to use
// the miner tip meaningfully as a splitter. // the miner tip meaningfully as a splitter.
// //
// - For the scenario where the pool does not contain non-executable blob txs
// anymore, it does not make sense to grant a later eviction priority to txs
// with high fee caps since it could enable pool wars. As such, any positive
// priority will be grouped together.
//
// priority = min(deltaBasefee, deltaBlobfee, 0)
//
// Optimisation tradeoffs: // Optimisation tradeoffs:
// //
// - Eviction relies on 3 fee minimums per account (exec tip, exec cap and blob // - Eviction relies on 3 fee minimums per account (exec tip, exec cap and blob
@ -470,6 +497,20 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
} }
p.evict = newPriceHeap(basefee, blobfee, p.index) p.evict = newPriceHeap(basefee, blobfee, p.index)
// Guess what was announced. This is needed because we don't want to
// participate in the diffusion of transactions where inclusion is blocked by
// a low base fee transaction. Since we don't persist that info, the best
// we can do is to assume that anything that could have been announced
// at current prices, actually was.
for addr := range p.index {
for _, tx := range p.index[addr] {
tx.announced = p.isAnnouncable(tx)
if !tx.announced {
break
}
}
}
// Pool initialized, attach the blob limbo to it to track blobs included // Pool initialized, attach the blob limbo to it to track blobs included
// recently but not yet finalized // recently but not yet finalized
p.limbo, err = newLimbo(p.chain.Config(), limbodir) p.limbo, err = newLimbo(p.chain.Config(), limbodir)
@ -517,6 +558,7 @@ func (p *BlobPool) Close() error {
// parseTransaction is a callback method on pool creation that gets called for // parseTransaction is a callback method on pool creation that gets called for
// each transaction on disk to create the in-memory metadata index. // each transaction on disk to create the in-memory metadata index.
// Announced state is not initialized here, it needs to be iniitalized seprately.
func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error { func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
tx := new(types.Transaction) tx := new(types.Transaction)
if err := rlp.DecodeBytes(blob, tx); err != nil { if err := rlp.DecodeBytes(blob, tx); err != nil {
@ -893,6 +935,37 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
} }
p.evict.reinit(basefee, blobfee, false) p.evict.reinit(basefee, blobfee, false)
// Announce transactions that became announcable due to fee changes
var announcable []*types.Transaction
for addr, txs := range p.index {
for i, meta := range txs {
if !meta.announced && (i == 0 || txs[i-1].announced) && p.isAnnouncable(meta) {
// Load the full transaction and strip the sidecar before announcing
// TODO: this is a bit ugly, as we have everything needed in meta already
data, err := p.store.Get(meta.id)
// Technically, we are supposed to set announced only if Get is successful.
// However, Get failing here indicates a more serious issue (data loss),
// so we set announced anyway to avoid repeated attempts.
meta.announced = true
if err != nil {
log.Error("Blobs missing for announcable transaction", "from", addr, "nonce", meta.nonce, "id", meta.id, "err", err)
continue
}
var tx types.Transaction
if err = rlp.DecodeBytes(data, &tx); err != nil {
log.Error("Blobs corrupted for announcable transaction", "from", addr, "nonce", meta.nonce, "id", meta.id, "err", err)
continue
}
announcable = append(announcable, tx.WithoutBlobTxSidecar())
log.Trace("Blob transaction now announcable", "from", addr, "nonce", meta.nonce, "id", meta.id, "hash", tx.Hash())
}
}
}
if len(announcable) > 0 {
p.discoverFeed.Send(core.NewTxsEvent{Txs: announcable})
}
// Update the basefee and blobfee metrics
basefeeGauge.Update(int64(basefee.Uint64())) basefeeGauge.Update(int64(basefee.Uint64()))
blobfeeGauge.Update(int64(blobfee.Uint64())) blobfeeGauge.Update(int64(blobfee.Uint64()))
p.updateStorageMetrics() p.updateStorageMetrics()
@ -1476,17 +1549,12 @@ func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int {
// Add inserts a set of blob transactions into the pool if they pass validation (both // Add inserts a set of blob transactions into the pool if they pass validation (both
// consensus validity and pool restrictions). // consensus validity and pool restrictions).
func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error { func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error {
var ( errs := make([]error, len(txs))
errs = make([]error, len(txs))
adds = make([]*types.Transaction, 0, len(txs))
)
for i, tx := range txs { for i, tx := range txs {
if errs[i] = p.ValidateTxBasics(tx); errs[i] != nil { if errs[i] = p.ValidateTxBasics(tx); errs[i] != nil {
continue continue
} }
if errs[i] = p.add(tx); errs[i] == nil { errs[i] = p.add(tx)
adds = append(adds, tx.WithoutBlobTxSidecar())
}
} }
return errs return errs
} }
@ -1678,9 +1746,15 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
addValidMeter.Mark(1) addValidMeter.Mark(1)
// Notify all listeners of the new arrival // Transaction was addded successfully, but we only announce if it is (close to being)
p.discoverFeed.Send(core.NewTxsEvent{Txs: []*types.Transaction{tx.WithoutBlobTxSidecar()}}) // includable and the previous one was already announced.
p.insertFeed.Send(core.NewTxsEvent{Txs: []*types.Transaction{tx.WithoutBlobTxSidecar()}}) if p.isAnnouncable(meta) && (meta.nonce == next || (len(txs) > 1 && txs[offset-1].announced)) {
meta.announced = true
p.discoverFeed.Send(core.NewTxsEvent{Txs: []*types.Transaction{tx.WithoutBlobTxSidecar()}})
p.insertFeed.Send(core.NewTxsEvent{Txs: []*types.Transaction{tx.WithoutBlobTxSidecar()}})
} else {
log.Trace("Blob transaction not announcable yet", "hash", tx.Hash(), "nonce", tx.Nonce())
}
//check the gapped queue for this account and try to promote //check the gapped queue for this account and try to promote
if gtxs, ok := p.gapped[from]; checkGapped && ok && len(gtxs) > 0 { if gtxs, ok := p.gapped[from]; checkGapped && ok && len(gtxs) > 0 {
@ -2004,6 +2078,12 @@ func (p *BlobPool) evictGapped() {
} }
} }
// isAnnouncable checks whether a transaction is announcable based on its
// fee parameters and announceThreshold.
func (p *BlobPool) isAnnouncable(meta *blobTxMeta) bool {
return evictionPriority(p.evict.basefeeJumps, meta.basefeeJumps, p.evict.blobfeeJumps, meta.blobfeeJumps) >= announceThreshold
}
// Stats retrieves the current pool stats, namely the number of pending and the // Stats retrieves the current pool stats, namely the number of pending and the
// number of queued (non-executable) transactions. // number of queued (non-executable) transactions.
func (p *BlobPool) Stats() (int, int) { func (p *BlobPool) Stats() (int, int) {

View file

@ -830,8 +830,8 @@ func TestOpenIndex(t *testing.T) {
//blobfeeJumps = []float64{34.023, 35.570, 36.879, 29.686, 26.243, 20.358} // log 1.125 (blob fee cap) //blobfeeJumps = []float64{34.023, 35.570, 36.879, 29.686, 26.243, 20.358} // log 1.125 (blob fee cap)
evictExecTipCaps = []uint64{10, 10, 5, 5, 1, 1} evictExecTipCaps = []uint64{10, 10, 5, 5, 1, 1}
evictExecFeeJumps = []float64{39.098, 38.204, 38.204, 19.549, 19.549, 19.549} // min(log 1.125 (exec fee cap)) evictExecFeeJumps = []float64{39.098, 38.204, 38.204, 19.549, 19.549, 19.549} // min(log 1.125 (exec fee cap))
evictBlobFeeJumps = []float64{34.023, 34.023, 34.023, 29.686, 26.243, 20.358} // min(log 1.125 (blob fee cap)) evictBlobFeeJumps = []float64{25.517256, 25.517256, 25.517256, 22.264502, 19.682646, 15.268934} // min(log 1.17 (blob fee cap))
totalSpent = uint256.NewInt(21000*(100+90+200+10+80+300) + blobSize*(55+66+77+33+22+11) + 100*6) // 21000 gas x price + 128KB x blobprice + value totalSpent = uint256.NewInt(21000*(100+90+200+10+80+300) + blobSize*(55+66+77+33+22+11) + 100*6) // 21000 gas x price + 128KB x blobprice + value
) )
@ -1751,8 +1751,8 @@ func TestAdd(t *testing.T) {
// Create a blob pool out of the pre-seeded dats // Create a blob pool out of the pre-seeded dats
chain := &testBlockChain{ chain := &testBlockChain{
config: params.MainnetChainConfig, config: params.MainnetChainConfig,
basefee: uint256.NewInt(1050), basefee: uint256.NewInt(1),
blobfee: uint256.NewInt(105), blobfee: uint256.NewInt(1),
statedb: statedb, statedb: statedb,
} }
pool := New(Config{Datadir: storage}, chain, nil) pool := New(Config{Datadir: storage}, chain, nil)

View file

@ -67,7 +67,7 @@ func newPriceHeap(basefee *uint256.Int, blobfee *uint256.Int, index map[common.A
func (h *evictHeap) reinit(basefee *uint256.Int, blobfee *uint256.Int, force bool) { func (h *evictHeap) reinit(basefee *uint256.Int, blobfee *uint256.Int, force bool) {
// If the update is mostly the same as the old, don't sort pointlessly // If the update is mostly the same as the old, don't sort pointlessly
basefeeJumps := dynamicFeeJumps(basefee) basefeeJumps := dynamicFeeJumps(basefee)
blobfeeJumps := dynamicFeeJumps(blobfee) blobfeeJumps := dynamicBlobFeeJumps(blobfee)
if !force && math.Abs(h.basefeeJumps-basefeeJumps) < 0.01 && math.Abs(h.blobfeeJumps-blobfeeJumps) < 0.01 { // TODO(karalabe): 0.01 enough, maybe should be smaller? Maybe this optimization is moot? if !force && math.Abs(h.basefeeJumps-basefeeJumps) < 0.01 && math.Abs(h.blobfeeJumps-blobfeeJumps) < 0.01 { // TODO(karalabe): 0.01 enough, maybe should be smaller? Maybe this optimization is moot?
return return
@ -95,13 +95,7 @@ func (h *evictHeap) Less(i, j int) bool {
lastJ := txsJ[len(txsJ)-1] lastJ := txsJ[len(txsJ)-1]
prioI := evictionPriority(h.basefeeJumps, lastI.evictionExecFeeJumps, h.blobfeeJumps, lastI.evictionBlobFeeJumps) prioI := evictionPriority(h.basefeeJumps, lastI.evictionExecFeeJumps, h.blobfeeJumps, lastI.evictionBlobFeeJumps)
if prioI > 0 {
prioI = 0
}
prioJ := evictionPriority(h.basefeeJumps, lastJ.evictionExecFeeJumps, h.blobfeeJumps, lastJ.evictionBlobFeeJumps) prioJ := evictionPriority(h.basefeeJumps, lastJ.evictionExecFeeJumps, h.blobfeeJumps, lastJ.evictionBlobFeeJumps)
if prioJ > 0 {
prioJ = 0
}
if prioI == prioJ { if prioI == prioJ {
return lastI.evictionExecTip.Lt(lastJ.evictionExecTip) return lastI.evictionExecTip.Lt(lastJ.evictionExecTip)
} }

View file

@ -109,22 +109,22 @@ func TestPriceHeapSorting(t *testing.T) {
order: []int{3, 2, 1, 0, 4, 5, 6}, order: []int{3, 2, 1, 0, 4, 5, 6},
}, },
// If both basefee and blobfee is specified, sort by the larger distance // If both basefee and blobfee is specified, sort by the larger distance
// of the two from the current network conditions, splitting same (loglog) // of the two from the current network conditions, splitting same
// ones via the tip. // ones via the tip.
// //
// Basefee: 1000 // Basefee: 1000 , jumps: 888, 790, 702, 624
// Blobfee: 100 // Blobfee: 100 , jumps: 85, 73, 62, 53
// //
// Tx #0: (800, 80) - 2 jumps below both => priority -1 // Tx #0: (800, 80) - 2 jumps below both => priority -2
// Tx #1: (630, 63) - 4 jumps below both => priority -2 // Tx #1: (630, 55) - 4 jumps below both => priority -4
// Tx #2: (800, 63) - 2 jumps below basefee, 4 jumps below blobfee => priority -2 (blob penalty dominates) // Tx #2: (800, 55) - 2 jumps below basefee, 4 jumps below blobfee => priority -4 (blob penalty dominates)
// Tx #3: (630, 80) - 4 jumps below basefee, 2 jumps below blobfee => priority -2 (base penalty dominates) // Tx #3: (630, 80) - 4 jumps below basefee, 2 jumps below blobfee => priority -4 (base penalty dominates)
// //
// Txs 1, 2, 3 share the same priority, split via tip, prefer 0 as the best // Txs 1, 2, 3 share the same priority, split via tip, prefer 0 as the best
{ {
execTips: []uint64{1, 2, 3, 4}, execTips: []uint64{1, 2, 3, 4},
execFees: []uint64{800, 630, 800, 630}, execFees: []uint64{800, 630, 800, 630},
blobFees: []uint64{80, 63, 63, 80}, blobFees: []uint64{80, 55, 55, 80},
basefee: 1000, basefee: 1000,
blobfee: 100, blobfee: 100,
order: []int{1, 2, 3, 0}, order: []int{1, 2, 3, 0},
@ -142,7 +142,7 @@ func TestPriceHeapSorting(t *testing.T) {
blobFee = uint256.NewInt(tt.blobFees[j]) blobFee = uint256.NewInt(tt.blobFees[j])
basefeeJumps = dynamicFeeJumps(execFee) basefeeJumps = dynamicFeeJumps(execFee)
blobfeeJumps = dynamicFeeJumps(blobFee) blobfeeJumps = dynamicBlobFeeJumps(blobFee)
) )
index[addr] = []*blobTxMeta{{ index[addr] = []*blobTxMeta{{
id: uint64(j), id: uint64(j),
@ -201,7 +201,7 @@ func benchmarkPriceHeapReinit(b *testing.B, datacap uint64) {
blobFee = uint256.NewInt(rnd.Uint64()) blobFee = uint256.NewInt(rnd.Uint64())
basefeeJumps = dynamicFeeJumps(execFee) basefeeJumps = dynamicFeeJumps(execFee)
blobfeeJumps = dynamicFeeJumps(blobFee) blobfeeJumps = dynamicBlobFeeJumps(blobFee)
) )
index[addr] = []*blobTxMeta{{ index[addr] = []*blobTxMeta{{
id: uint64(i), id: uint64(i),
@ -277,7 +277,7 @@ func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) {
blobFee = uint256.NewInt(rnd.Uint64()) blobFee = uint256.NewInt(rnd.Uint64())
basefeeJumps = dynamicFeeJumps(execFee) basefeeJumps = dynamicFeeJumps(execFee)
blobfeeJumps = dynamicFeeJumps(blobFee) blobfeeJumps = dynamicBlobFeeJumps(blobFee)
) )
index[addr] = []*blobTxMeta{{ index[addr] = []*blobTxMeta{{
id: uint64(i), id: uint64(i),
@ -308,7 +308,7 @@ func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) {
blobFee = uint256.NewInt(rnd.Uint64()) blobFee = uint256.NewInt(rnd.Uint64())
basefeeJumps = dynamicFeeJumps(execFee) basefeeJumps = dynamicFeeJumps(execFee)
blobfeeJumps = dynamicFeeJumps(blobFee) blobfeeJumps = dynamicBlobFeeJumps(blobFee)
) )
metas[i] = &blobTxMeta{ metas[i] = &blobTxMeta{
id: uint64(int(blobs) + i), id: uint64(int(blobs) + i),

View file

@ -18,7 +18,6 @@ package blobpool
import ( import (
"math" "math"
"math/bits"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -26,6 +25,13 @@ import (
// log1_125 is used in the eviction priority calculation. // log1_125 is used in the eviction priority calculation.
var log1_125 = math.Log(1.125) var log1_125 = math.Log(1.125)
// log1_17 is used in the eviction priority calculation for blob fees.
// EIP-7892 (BPO) changed the ratio of target to max blobs, and with that
// also the maximum blob fee decrease in a slot from 1.125 to approx 1.17 .
// Since we want priorities to approximate time, we should change our log
// calculation for blob fees.
var log1_17 = log1_125 * 4 / 3
// evictionPriority calculates the eviction priority based on the algorithm // evictionPriority calculates the eviction priority based on the algorithm
// described in the BlobPool docs for both fee components. // described in the BlobPool docs for both fee components.
// //
@ -36,23 +42,20 @@ func evictionPriority(basefeeJumps float64, txBasefeeJumps, blobfeeJumps, txBlob
basefeePriority = evictionPriority1D(basefeeJumps, txBasefeeJumps) basefeePriority = evictionPriority1D(basefeeJumps, txBasefeeJumps)
blobfeePriority = evictionPriority1D(blobfeeJumps, txBlobfeeJumps) blobfeePriority = evictionPriority1D(blobfeeJumps, txBlobfeeJumps)
) )
if basefeePriority < blobfeePriority { return min(0, basefeePriority, blobfeePriority)
return basefeePriority
}
return blobfeePriority
} }
// evictionPriority1D calculates the eviction priority based on the algorithm // evictionPriority1D calculates the eviction priority based on the algorithm
// described in the BlobPool docs for a single fee component. // described in the BlobPool docs for a single fee component.
func evictionPriority1D(basefeeJumps float64, txfeeJumps float64) int { func evictionPriority1D(basefeeJumps float64, txfeeJumps float64) int {
jumps := txfeeJumps - basefeeJumps jumps := txfeeJumps - basefeeJumps
if int(jumps) == 0 { if jumps <= 0 {
return 0 // can't log2 0 return int(math.Floor(jumps))
} }
if jumps < 0 { // We only use the negative part for ordering. The positive part is only used
return -intLog2(uint(-math.Floor(jumps))) // for threshold comparison (with a negative threshold), so the value is almost
} // irrelevant, as long as it's positive.
return intLog2(uint(math.Ceil(jumps))) return int((math.Ceil(jumps)))
} }
// dynamicFeeJumps calculates the log1.125(fee), namely the number of fee jumps // dynamicFeeJumps calculates the log1.125(fee), namely the number of fee jumps
@ -70,21 +73,9 @@ func dynamicFeeJumps(fee *uint256.Int) float64 {
return math.Log(fee.Float64()) / log1_125 return math.Log(fee.Float64()) / log1_125
} }
// intLog2 is a helper to calculate the integral part of a log2 of an unsigned func dynamicBlobFeeJumps(fee *uint256.Int) float64 {
// integer. It is a very specific calculation that's not particularly useful in if fee.IsZero() {
// general, but it's what we need here (it's fast). return 0 // can't log2 zero, should never happen outside tests, but don't choke
func intLog2(n uint) int {
switch {
case n == 0:
panic("log2(0) is undefined")
case n < 2048:
return bits.UintSize - bits.LeadingZeros(n) - 1
default:
// The input is log1.125(uint256) = log2(uint256) / log2(1.125). At the
// most extreme, log2(uint256) will be a bit below 257, and the constant
// log2(1.125) ~= 0.17. The larges input thus is ~257 / ~0.17 ~= ~1511.
panic("dynamic fee jump diffs cannot reach this")
} }
return math.Log(fee.Float64()) / log1_17
} }

View file

@ -30,12 +30,12 @@ func TestPriorityCalculation(t *testing.T) {
txfee uint64 txfee uint64
result int result int
}{ }{
{basefee: 7, txfee: 10, result: 2}, // 3.02 jumps, 4 ceil, 2 log2 {basefee: 7, txfee: 10, result: 4}, // 3.02 jumps, 4 ceil
{basefee: 17_200_000_000, txfee: 17_200_000_000, result: 0}, // 0 jumps, special case 0 log2 {basefee: 17_200_000_000, txfee: 17_200_000_000, result: 0}, // 0 jumps, special case 0
{basefee: 9_853_941_692, txfee: 11_085_092_510, result: 0}, // 0.99 jumps, 1 ceil, 0 log2 {basefee: 9_853_941_692, txfee: 11_085_092_510, result: 1}, // 0.99 jumps, 1 ceil
{basefee: 11_544_106_391, txfee: 10_356_781_100, result: 0}, // -0.92 jumps, -1 floor, 0 log2 {basefee: 11_544_106_391, txfee: 10_356_781_100, result: -1}, // -0.92 jumps, -1 floor
{basefee: 17_200_000_000, txfee: 7, result: -7}, // -183.57 jumps, -184 floor, -7 log2 {basefee: 17_200_000_000, txfee: 7, result: -184}, // -183.57 jumps, -184 floor
{basefee: 7, txfee: 17_200_000_000, result: 7}, // 183.57 jumps, 184 ceil, 7 log2 {basefee: 7, txfee: 17_200_000_000, result: 184}, // 183.57 jumps, 184 ceil
} }
for i, tt := range tests { for i, tt := range tests {
var ( var (
@ -69,7 +69,7 @@ func BenchmarkPriorityCalculation(b *testing.B) {
blobfee := uint256.NewInt(123_456_789_000) // Completely random, no idea what this will be blobfee := uint256.NewInt(123_456_789_000) // Completely random, no idea what this will be
basefeeJumps := dynamicFeeJumps(basefee) basefeeJumps := dynamicFeeJumps(basefee)
blobfeeJumps := dynamicFeeJumps(blobfee) blobfeeJumps := dynamicBlobFeeJumps(blobfee)
// The transaction's fee cap and blob fee cap are constant across the life // The transaction's fee cap and blob fee cap are constant across the life
// of the transaction, so we can pre-calculate and cache them. // of the transaction, so we can pre-calculate and cache them.
@ -77,7 +77,7 @@ func BenchmarkPriorityCalculation(b *testing.B) {
txBlobfeeJumps := make([]float64, b.N) txBlobfeeJumps := make([]float64, b.N)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
txBasefeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rnd.Uint64())) txBasefeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rnd.Uint64()))
txBlobfeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rnd.Uint64())) txBlobfeeJumps[i] = dynamicBlobFeeJumps(uint256.NewInt(rnd.Uint64()))
} }
b.ResetTimer() b.ResetTimer()
b.ReportAllocs() b.ReportAllocs()

View file

@ -68,6 +68,43 @@ func TestListAddVeryExpensive(t *testing.T) {
} }
} }
// TestPriceHeapCmp tests that the price heap comparison function works as intended.
// It also tests combinations where the basefee is higher than the gas fee cap, which
// are useful to sort in the mempool to support basefee changes.
func TestPriceHeapCmp(t *testing.T) {
key, _ := crypto.GenerateKey()
txs := []*types.Transaction{
// nonce, gaslimit, gasfee, gastip
dynamicFeeTx(0, 1000, big.NewInt(2), big.NewInt(1), key),
dynamicFeeTx(0, 1000, big.NewInt(1), big.NewInt(2), key),
dynamicFeeTx(0, 1000, big.NewInt(1), big.NewInt(1), key),
dynamicFeeTx(0, 1000, big.NewInt(1), big.NewInt(0), key),
}
// create priceHeap
ph := &priceHeap{}
// now set the basefee on the heap
for _, basefee := range []uint64{0, 1, 2, 3} {
ph.baseFee = uint256.NewInt(basefee)
for i := 0; i < len(txs); i++ {
for j := 0; j < len(txs); j++ {
switch {
case i == j:
if c := ph.cmp(txs[i], txs[j]); c != 0 {
t.Errorf("tx %d should be equal priority to tx %d with basefee %d (cmp=%d)", i, j, basefee, c)
}
case i < j:
if c := ph.cmp(txs[i], txs[j]); c != 1 {
t.Errorf("tx %d vs tx %d comparison inconsistent with basefee %d (cmp=%d)", i, j, basefee, c)
}
}
}
}
}
}
func BenchmarkListAdd(b *testing.B) { func BenchmarkListAdd(b *testing.B) {
// Generate a list of transactions to insert // Generate a list of transactions to insert
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()

View file

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -86,8 +87,10 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
return fmt.Errorf("%w: type %d rejected, pool not yet in Prague", core.ErrTxTypeNotSupported, tx.Type()) return fmt.Errorf("%w: type %d rejected, pool not yet in Prague", core.ErrTxTypeNotSupported, tx.Type())
} }
// Check whether the init code size has been exceeded // Check whether the init code size has been exceeded
if rules.IsShanghai && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { if tx.To() == nil {
return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize) if err := vm.CheckMaxInitCodeSize(&rules, uint64(len(tx.Data()))); err != nil {
return err
}
} }
if rules.IsOsaka && tx.Gas() > params.MaxTxGas { if rules.IsOsaka && tx.Gas() > params.MaxTxGas {
return fmt.Errorf("%w (cap: %d, tx: %d)", core.ErrGasLimitTooHigh, params.MaxTxGas, tx.Gas()) return fmt.Errorf("%w (cap: %d, tx: %d)", core.ErrGasLimitTooHigh, params.MaxTxGas, tx.Gas())

View file

@ -24,13 +24,6 @@ import (
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
// CodeChange contains the runtime bytecode deployed at an address and the
// transaction index where the deployment took place.
type CodeChange struct {
TxIndex uint16
Code []byte `json:"code,omitempty"`
}
// ConstructionAccountAccess contains post-block account state for mutations as well as // ConstructionAccountAccess contains post-block account state for mutations as well as
// all storage keys that were read during execution. It is used when building block // all storage keys that were read during execution. It is used when building block
// access list during execution. // access list during execution.
@ -55,9 +48,9 @@ type ConstructionAccountAccess struct {
// by tx index. // by tx index.
NonceChanges map[uint16]uint64 `json:"nonceChanges,omitempty"` NonceChanges map[uint16]uint64 `json:"nonceChanges,omitempty"`
// CodeChange is only set for contract accounts which were deployed in // CodeChange contains the post-state contract code of an account keyed
// the block. // by tx index.
CodeChange *CodeChange `json:"codeChange,omitempty"` CodeChange map[uint16][]byte `json:"codeChange,omitempty"`
} }
// NewConstructionAccountAccess initializes the account access object. // NewConstructionAccountAccess initializes the account access object.
@ -67,6 +60,7 @@ func NewConstructionAccountAccess() *ConstructionAccountAccess {
StorageReads: make(map[common.Hash]struct{}), StorageReads: make(map[common.Hash]struct{}),
BalanceChanges: make(map[uint16]*uint256.Int), BalanceChanges: make(map[uint16]*uint256.Int),
NonceChanges: make(map[uint16]uint64), NonceChanges: make(map[uint16]uint64),
CodeChange: make(map[uint16][]byte),
} }
} }
@ -120,10 +114,8 @@ func (b *ConstructionBlockAccessList) CodeChange(address common.Address, txIndex
if _, ok := b.Accounts[address]; !ok { if _, ok := b.Accounts[address]; !ok {
b.Accounts[address] = NewConstructionAccountAccess() b.Accounts[address] = NewConstructionAccountAccess()
} }
b.Accounts[address].CodeChange = &CodeChange{ // TODO(rjl493456442) is it essential to deep-copy the code?
TxIndex: txIndex, b.Accounts[address].CodeChange[txIndex] = bytes.Clone(code)
Code: bytes.Clone(code),
}
} }
// NonceChange records tx post-state nonce of any contract-like accounts whose // NonceChange records tx post-state nonce of any contract-like accounts whose
@ -170,12 +162,11 @@ func (b *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList {
aaCopy.BalanceChanges = balances aaCopy.BalanceChanges = balances
aaCopy.NonceChanges = maps.Clone(aa.NonceChanges) aaCopy.NonceChanges = maps.Clone(aa.NonceChanges)
if aa.CodeChange != nil { codes := make(map[uint16][]byte, len(aa.CodeChange))
aaCopy.CodeChange = &CodeChange{ for index, code := range aa.CodeChange {
TxIndex: aa.CodeChange.TxIndex, codes[index] = bytes.Clone(code)
Code: bytes.Clone(aa.CodeChange.Code),
}
} }
aaCopy.CodeChange = codes
res.Accounts[addr] = &aaCopy res.Accounts[addr] = &aaCopy
} }
return &res return &res

View file

@ -119,6 +119,13 @@ func (e *encodingSlotWrites) validate() error {
return errors.New("storage write tx indices not in order") return errors.New("storage write tx indices not in order")
} }
// encodingCodeChange contains the runtime bytecode deployed at an address
// and the transaction index where the deployment took place.
type encodingCodeChange struct {
TxIndex uint16 `ssz-size:"2"`
Code []byte `ssz-max:"300000"` // TODO(rjl493456442) shall we put the limit here? The limit will be increased gradually
}
// AccountAccess is the encoding format of ConstructionAccountAccess. // AccountAccess is the encoding format of ConstructionAccountAccess.
type AccountAccess struct { type AccountAccess struct {
Address [20]byte `ssz-size:"20"` // 20-byte Ethereum address Address [20]byte `ssz-size:"20"` // 20-byte Ethereum address
@ -126,7 +133,7 @@ type AccountAccess struct {
StorageReads [][32]byte `ssz-max:"300000"` // Read-only storage keys StorageReads [][32]byte `ssz-max:"300000"` // Read-only storage keys
BalanceChanges []encodingBalanceChange `ssz-max:"300000"` // Balance changes ([tx_index -> post_balance]) BalanceChanges []encodingBalanceChange `ssz-max:"300000"` // Balance changes ([tx_index -> post_balance])
NonceChanges []encodingAccountNonce `ssz-max:"300000"` // Nonce changes ([tx_index -> new_nonce]) NonceChanges []encodingAccountNonce `ssz-max:"300000"` // Nonce changes ([tx_index -> new_nonce])
Code []CodeChange `ssz-max:"1"` // Code changes ([tx_index -> new_code]) CodeChanges []encodingCodeChange `ssz-max:"300000"` // Code changes ([tx_index -> new_code])
} }
// validate converts the account accesses out of encoding format. // validate converts the account accesses out of encoding format.
@ -166,9 +173,16 @@ func (e *AccountAccess) validate() error {
return errors.New("nonce changes not in ascending order by tx index") return errors.New("nonce changes not in ascending order by tx index")
} }
// Convert code change // Check the code changes are sorted in order
if len(e.Code) == 1 { if !slices.IsSortedFunc(e.CodeChanges, func(a, b encodingCodeChange) int {
if len(e.Code[0].Code) > params.MaxCodeSize { return cmp.Compare[uint16](a.TxIndex, b.TxIndex)
}) {
return errors.New("code changes not in ascending order by tx index")
}
for _, change := range e.CodeChanges {
// TODO(rjl493456442): This check should be fork-aware, since the limit may
// differ across forks.
if len(change.Code) > params.MaxCodeSize {
return errors.New("code change contained oversized code") return errors.New("code change contained oversized code")
} }
} }
@ -182,6 +196,8 @@ func (e *AccountAccess) Copy() AccountAccess {
StorageReads: slices.Clone(e.StorageReads), StorageReads: slices.Clone(e.StorageReads),
BalanceChanges: slices.Clone(e.BalanceChanges), BalanceChanges: slices.Clone(e.BalanceChanges),
NonceChanges: slices.Clone(e.NonceChanges), NonceChanges: slices.Clone(e.NonceChanges),
StorageWrites: make([]encodingSlotWrites, 0, len(e.StorageWrites)),
CodeChanges: make([]encodingCodeChange, 0, len(e.CodeChanges)),
} }
for _, storageWrite := range e.StorageWrites { for _, storageWrite := range e.StorageWrites {
res.StorageWrites = append(res.StorageWrites, encodingSlotWrites{ res.StorageWrites = append(res.StorageWrites, encodingSlotWrites{
@ -189,13 +205,11 @@ func (e *AccountAccess) Copy() AccountAccess {
Accesses: slices.Clone(storageWrite.Accesses), Accesses: slices.Clone(storageWrite.Accesses),
}) })
} }
if len(e.Code) == 1 { for _, codeChange := range e.CodeChanges {
res.Code = []CodeChange{ res.CodeChanges = append(res.CodeChanges, encodingCodeChange{
{ TxIndex: codeChange.TxIndex,
e.Code[0].TxIndex, Code: bytes.Clone(codeChange.Code),
bytes.Clone(e.Code[0].Code), })
},
}
} }
return res return res
} }
@ -212,11 +226,11 @@ var _ rlp.Encoder = &ConstructionBlockAccessList{}
func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAccess { func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAccess {
res := AccountAccess{ res := AccountAccess{
Address: addr, Address: addr,
StorageWrites: make([]encodingSlotWrites, 0), StorageWrites: make([]encodingSlotWrites, 0, len(a.StorageWrites)),
StorageReads: make([][32]byte, 0), StorageReads: make([][32]byte, 0, len(a.StorageReads)),
BalanceChanges: make([]encodingBalanceChange, 0), BalanceChanges: make([]encodingBalanceChange, 0, len(a.BalanceChanges)),
NonceChanges: make([]encodingAccountNonce, 0), NonceChanges: make([]encodingAccountNonce, 0, len(a.NonceChanges)),
Code: nil, CodeChanges: make([]encodingCodeChange, 0, len(a.CodeChange)),
} }
// Convert write slots // Convert write slots
@ -268,13 +282,13 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc
} }
// Convert code change // Convert code change
if a.CodeChange != nil { codeIndices := slices.Collect(maps.Keys(a.CodeChange))
res.Code = []CodeChange{ slices.SortFunc(codeIndices, cmp.Compare[uint16])
{ for _, idx := range codeIndices {
a.CodeChange.TxIndex, res.CodeChanges = append(res.CodeChanges, encodingCodeChange{
bytes.Clone(a.CodeChange.Code), TxIndex: idx,
}, Code: a.CodeChange[idx],
} })
} }
return res return res
} }
@ -327,9 +341,9 @@ func (e *BlockAccessList) PrettyPrint() string {
printWithIndent(2, fmt.Sprintf("%d: %d", change.TxIdx, change.Nonce)) printWithIndent(2, fmt.Sprintf("%d: %d", change.TxIdx, change.Nonce))
} }
if len(accountDiff.Code) > 0 { printWithIndent(1, "code changes:")
printWithIndent(1, "code:") for _, change := range accountDiff.CodeChanges {
printWithIndent(2, fmt.Sprintf("%d: %x", accountDiff.Code[0].TxIndex, accountDiff.Code[0].Code)) printWithIndent(2, fmt.Sprintf("%d: %x", change.TxIndex, change.Code))
} }
} }
return res.String() return res.String()

View file

@ -49,7 +49,7 @@ func (obj *BlockAccessList) EncodeRLP(_w io.Writer) error {
} }
w.ListEnd(_tmp15) w.ListEnd(_tmp15)
_tmp18 := w.List() _tmp18 := w.List()
for _, _tmp19 := range _tmp2.Code { for _, _tmp19 := range _tmp2.CodeChanges {
_tmp20 := w.List() _tmp20 := w.List()
w.WriteUint64(uint64(_tmp19.TxIndex)) w.WriteUint64(uint64(_tmp19.TxIndex))
w.WriteBytes(_tmp19.Code) w.WriteBytes(_tmp19.Code)
@ -228,13 +228,13 @@ func (obj *BlockAccessList) DecodeRLP(dec *rlp.Stream) error {
return err return err
} }
_tmp2.NonceChanges = _tmp17 _tmp2.NonceChanges = _tmp17
// Code: // CodeChanges:
var _tmp21 []CodeChange var _tmp21 []encodingCodeChange
if _, err := dec.List(); err != nil { if _, err := dec.List(); err != nil {
return err return err
} }
for dec.MoreDataInList() { for dec.MoreDataInList() {
var _tmp22 CodeChange var _tmp22 encodingCodeChange
{ {
if _, err := dec.List(); err != nil { if _, err := dec.List(); err != nil {
return err return err
@ -260,7 +260,7 @@ func (obj *BlockAccessList) DecodeRLP(dec *rlp.Stream) error {
if err := dec.ListEnd(); err != nil { if err := dec.ListEnd(); err != nil {
return err return err
} }
_tmp2.Code = _tmp21 _tmp2.CodeChanges = _tmp21
if err := dec.ListEnd(); err != nil { if err := dec.ListEnd(); err != nil {
return err return err
} }

View file

@ -60,9 +60,8 @@ func makeTestConstructionBAL() *ConstructionBlockAccessList {
1: 2, 1: 2,
2: 6, 2: 6,
}, },
CodeChange: &CodeChange{ CodeChange: map[uint16][]byte{
TxIndex: 0, 0: common.Hex2Bytes("deadbeef"),
Code: common.Hex2Bytes("deadbeef"),
}, },
}, },
common.BytesToAddress([]byte{0xff, 0xff, 0xff}): { common.BytesToAddress([]byte{0xff, 0xff, 0xff}): {
@ -85,6 +84,9 @@ func makeTestConstructionBAL() *ConstructionBlockAccessList {
NonceChanges: map[uint16]uint64{ NonceChanges: map[uint16]uint64{
1: 2, 1: 2,
}, },
CodeChange: map[uint16][]byte{
0: common.Hex2Bytes("deadbeef"),
},
}, },
}, },
} }
@ -179,7 +181,7 @@ func makeTestAccountAccess(sort bool) AccountAccess {
StorageReads: storageReads, StorageReads: storageReads,
BalanceChanges: balances, BalanceChanges: balances,
NonceChanges: nonces, NonceChanges: nonces,
Code: []CodeChange{ CodeChanges: []encodingCodeChange{
{ {
TxIndex: 100, TxIndex: 100,
Code: testrand.Bytes(256), Code: testrand.Bytes(256),

View file

@ -98,6 +98,9 @@ type Header struct {
// RequestsHash was added by EIP-7685 and is ignored in legacy headers. // RequestsHash was added by EIP-7685 and is ignored in legacy headers.
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
// SlotNumber was added by EIP-7843 and is ignored in legacy headers.
SlotNumber *uint64 `json:"slotNumber" rlp:"optional"`
} }
// field type overrides for gencodec // field type overrides for gencodec
@ -112,6 +115,7 @@ type headerMarshaling struct {
Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON
BlobGasUsed *hexutil.Uint64 BlobGasUsed *hexutil.Uint64
ExcessBlobGas *hexutil.Uint64 ExcessBlobGas *hexutil.Uint64
SlotNumber *hexutil.Uint64
} }
// Hash returns the block hash of the header, which is simply the keccak256 hash of its // Hash returns the block hash of the header, which is simply the keccak256 hash of its
@ -316,6 +320,10 @@ func CopyHeader(h *Header) *Header {
cpy.RequestsHash = new(common.Hash) cpy.RequestsHash = new(common.Hash)
*cpy.RequestsHash = *h.RequestsHash *cpy.RequestsHash = *h.RequestsHash
} }
if h.SlotNumber != nil {
cpy.SlotNumber = new(uint64)
*cpy.SlotNumber = *h.SlotNumber
}
return &cpy return &cpy
} }
@ -416,6 +424,15 @@ func (b *Block) BlobGasUsed() *uint64 {
return blobGasUsed return blobGasUsed
} }
func (b *Block) SlotNumber() *uint64 {
var slotNum *uint64
if b.header.SlotNumber != nil {
slotNum = new(uint64)
*slotNum = *b.header.SlotNumber
}
return slotNum
}
// Size returns the true RLP encoded storage size of the block, either by encoding // Size returns the true RLP encoded storage size of the block, either by encoding
// and returning it, or returning a previously cached value. // and returning it, or returning a previously cached value.
func (b *Block) Size() uint64 { func (b *Block) Size() uint64 {

View file

@ -37,6 +37,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
SlotNumber *hexutil.Uint64 `json:"slotNumber" rlp:"optional"`
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
} }
var enc Header var enc Header
@ -61,6 +62,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas) enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
enc.ParentBeaconRoot = h.ParentBeaconRoot enc.ParentBeaconRoot = h.ParentBeaconRoot
enc.RequestsHash = h.RequestsHash enc.RequestsHash = h.RequestsHash
enc.SlotNumber = (*hexutil.Uint64)(h.SlotNumber)
enc.Hash = h.Hash() enc.Hash = h.Hash()
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -89,6 +91,7 @@ func (h *Header) UnmarshalJSON(input []byte) error {
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
SlotNumber *hexutil.Uint64 `json:"slotNumber" rlp:"optional"`
} }
var dec Header var dec Header
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -169,5 +172,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
if dec.RequestsHash != nil { if dec.RequestsHash != nil {
h.RequestsHash = dec.RequestsHash h.RequestsHash = dec.RequestsHash
} }
if dec.SlotNumber != nil {
h.SlotNumber = (*uint64)(dec.SlotNumber)
}
return nil return nil
} }

View file

@ -43,7 +43,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
_tmp4 := obj.ExcessBlobGas != nil _tmp4 := obj.ExcessBlobGas != nil
_tmp5 := obj.ParentBeaconRoot != nil _tmp5 := obj.ParentBeaconRoot != nil
_tmp6 := obj.RequestsHash != nil _tmp6 := obj.RequestsHash != nil
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 { _tmp7 := obj.SlotNumber != nil
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.BaseFee == nil { if obj.BaseFee == nil {
w.Write(rlp.EmptyString) w.Write(rlp.EmptyString)
} else { } else {
@ -53,41 +54,48 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
w.WriteBigInt(obj.BaseFee) w.WriteBigInt(obj.BaseFee)
} }
} }
if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 { if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.WithdrawalsHash == nil { if obj.WithdrawalsHash == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.WithdrawalsHash[:]) w.WriteBytes(obj.WithdrawalsHash[:])
} }
} }
if _tmp3 || _tmp4 || _tmp5 || _tmp6 { if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.BlobGasUsed == nil { if obj.BlobGasUsed == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteUint64((*obj.BlobGasUsed)) w.WriteUint64((*obj.BlobGasUsed))
} }
} }
if _tmp4 || _tmp5 || _tmp6 { if _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.ExcessBlobGas == nil { if obj.ExcessBlobGas == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteUint64((*obj.ExcessBlobGas)) w.WriteUint64((*obj.ExcessBlobGas))
} }
} }
if _tmp5 || _tmp6 { if _tmp5 || _tmp6 || _tmp7 {
if obj.ParentBeaconRoot == nil { if obj.ParentBeaconRoot == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.ParentBeaconRoot[:]) w.WriteBytes(obj.ParentBeaconRoot[:])
} }
} }
if _tmp6 { if _tmp6 || _tmp7 {
if obj.RequestsHash == nil { if obj.RequestsHash == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.RequestsHash[:]) w.WriteBytes(obj.RequestsHash[:])
} }
} }
if _tmp7 {
if obj.SlotNumber == nil {
w.Write([]byte{0x80})
} else {
w.WriteUint64((*obj.SlotNumber))
}
}
w.ListEnd(_tmp0) w.ListEnd(_tmp0)
return w.Flush() return w.Flush()
} }

View file

@ -317,11 +317,15 @@ func (tx *Transaction) To() *common.Address {
// Cost returns (gas * gasPrice) + (blobGas * blobGasPrice) + value. // Cost returns (gas * gasPrice) + (blobGas * blobGasPrice) + value.
func (tx *Transaction) Cost() *big.Int { func (tx *Transaction) Cost() *big.Int {
total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) // Avoid allocating copies via tx.GasPrice()/tx.Value(); use inner values directly.
if tx.Type() == BlobTxType { total := new(big.Int).SetUint64(tx.inner.gas())
total.Add(total, new(big.Int).Mul(tx.BlobGasFeeCap(), new(big.Int).SetUint64(tx.BlobGas()))) total.Mul(total, tx.inner.gasPrice())
if blobtx, ok := tx.inner.(*BlobTx); ok {
tmp := new(big.Int).SetUint64(blobtx.blobGas())
tmp.Mul(tmp, blobtx.BlobFeeCap.ToBig())
total.Add(total, tmp)
} }
total.Add(total, tx.Value()) total.Add(total, tx.inner.value())
return total return total
} }
@ -396,14 +400,37 @@ func (tx *Transaction) calcEffectiveGasTip(dst *uint256.Int, baseFee *uint256.In
return err return err
} }
// EffectiveGasTipValue returns the effective gasTip value for the given base fee,
// even if it would be negative. This can be used for sorting purposes.
func (tx *Transaction) EffectiveGasTipValue(baseFee *big.Int) *big.Int {
// min(gasTipCap, gasFeeCap - baseFee)
dst := new(big.Int)
if baseFee == nil {
dst.Set(tx.inner.gasTipCap())
return dst
}
dst.Sub(tx.inner.gasFeeCap(), baseFee) // gasFeeCap - baseFee
gasTipCap := tx.inner.gasTipCap()
if gasTipCap.Cmp(dst) < 0 { // gasTipCap < (gasFeeCap - baseFee)
dst.Set(gasTipCap)
}
return dst
}
func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *uint256.Int) int { func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *uint256.Int) int {
if baseFee == nil { if baseFee == nil {
return tx.GasTipCapCmp(other) return tx.GasTipCapCmp(other)
} }
// Use more efficient internal method. // Use more efficient internal method.
txTip, otherTip := new(uint256.Int), new(uint256.Int) txTip, otherTip := new(uint256.Int), new(uint256.Int)
tx.calcEffectiveGasTip(txTip, baseFee) err1 := tx.calcEffectiveGasTip(txTip, baseFee)
other.calcEffectiveGasTip(otherTip, baseFee) err2 := other.calcEffectiveGasTip(otherTip, baseFee)
if err1 != nil || err2 != nil {
// fall back to big int comparison in case of error
base := baseFee.ToBig()
return tx.EffectiveGasTipValue(base).Cmp(other.EffectiveGasTipValue(base))
}
return txTip.Cmp(otherTip) return txTip.Cmp(otherTip)
} }

View file

@ -17,12 +17,43 @@
package vm package vm
import ( import (
"fmt"
"math" "math"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
// CheckMaxInitCodeSize checks the size of contract initcode against the protocol-defined limit.
func CheckMaxInitCodeSize(rules *params.Rules, size uint64) error {
if rules.IsAmsterdam {
if size > params.MaxInitCodeSizeAmsterdam {
return fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, size, params.MaxInitCodeSizeAmsterdam)
}
} else if rules.IsShanghai {
if size > params.MaxInitCodeSize {
return fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, size, params.MaxInitCodeSize)
}
}
return nil
}
// CheckMaxCodeSize checks the size of contract code against the protocol-defined limit.
func CheckMaxCodeSize(rules *params.Rules, size uint64) error {
if rules.IsAmsterdam {
if size > params.MaxCodeSizeAmsterdam {
return fmt.Errorf("%w: code size %v limit %v", ErrMaxCodeSizeExceeded, size, params.MaxCodeSizeAmsterdam)
}
} else if rules.IsEIP158 {
if size > params.MaxCodeSize {
return fmt.Errorf("%w: code size %v limit %v", ErrMaxCodeSizeExceeded, size, params.MaxCodeSize)
}
}
return nil
}
// calcMemSize64 calculates the required memory size, and returns // calcMemSize64 calculates the required memory size, and returns
// the size and whether the result overflowed uint64 // the size and whether the result overflowed uint64
func calcMemSize64(off, l *uint256.Int) (uint64, bool) { func calcMemSize64(off, l *uint256.Int) (uint64, bool) {

View file

@ -262,7 +262,7 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
// - the returned bytes, // - the returned bytes,
// - the _remaining_ gas, // - the _remaining_ gas,
// - any error that occurred // - any error that occurred
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, logger *tracing.Hooks) (ret []byte, remainingGas uint64, err error) { func RunPrecompiledContract(stateDB StateDB, p PrecompiledContract, address common.Address, input []byte, suppliedGas uint64, logger *tracing.Hooks) (ret []byte, remainingGas uint64, err error) {
gasCost := p.RequiredGas(input) gasCost := p.RequiredGas(input)
if suppliedGas < gasCost { if suppliedGas < gasCost {
return nil, 0, ErrOutOfGas return nil, 0, ErrOutOfGas
@ -271,6 +271,12 @@ func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uin
logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract) logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract)
} }
suppliedGas -= gasCost suppliedGas -= gasCost
// Touch the precompile for block-level accessList recording once Amsterdam
// fork is activated.
if stateDB != nil {
stateDB.Exist(address)
}
output, err := p.Run(input) output, err := p.Run(input)
return output, suppliedGas, err return output, suppliedGas, err
} }

View file

@ -36,7 +36,7 @@ func FuzzPrecompiledContracts(f *testing.F) {
return return
} }
inWant := string(input) inWant := string(input)
RunPrecompiledContract(p, input, gas, nil) RunPrecompiledContract(nil, p, a, input, gas, nil)
if inHave := string(input); inWant != inHave { if inHave := string(input); inWant != inHave {
t.Errorf("Precompiled %v modified input data", a) t.Errorf("Precompiled %v modified input data", a)
} }

View file

@ -99,7 +99,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
if res, _, err := RunPrecompiledContract(p, in, gas, nil); err != nil { if res, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, gas, nil); err != nil {
t.Error(err) t.Error(err)
} else if common.Bytes2Hex(res) != test.Expected { } else if common.Bytes2Hex(res) != test.Expected {
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
@ -121,7 +121,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
gas := test.Gas - 1 gas := test.Gas - 1
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
_, _, err := RunPrecompiledContract(p, in, gas, nil) _, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, gas, nil)
if err.Error() != "out of gas" { if err.Error() != "out of gas" {
t.Errorf("Expected error [out of gas], got [%v]", err) t.Errorf("Expected error [out of gas], got [%v]", err)
} }
@ -138,7 +138,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(test.Name, func(t *testing.T) { t.Run(test.Name, func(t *testing.T) {
_, _, err := RunPrecompiledContract(p, in, gas, nil) _, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, gas, nil)
if err.Error() != test.ExpectedError { if err.Error() != test.ExpectedError {
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
} }
@ -169,7 +169,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
start := time.Now() start := time.Now()
for bench.Loop() { for bench.Loop() {
copy(data, in) copy(data, in)
res, _, err = RunPrecompiledContract(p, data, reqGas, nil) res, _, err = RunPrecompiledContract(nil, p, common.HexToAddress(addr), data, reqGas, nil)
} }
elapsed := uint64(time.Since(start)) elapsed := uint64(time.Since(start))
if elapsed < 1 { if elapsed < 1 {

View file

@ -43,6 +43,7 @@ var activators = map[int]func(*JumpTable){
7702: enable7702, 7702: enable7702,
7939: enable7939, 7939: enable7939,
8024: enable8024, 8024: enable8024,
7843: enable7843,
} }
// EnableEIP enables the given EIP on the config. // EnableEIP enables the given EIP on the config.
@ -579,3 +580,19 @@ func enable7702(jt *JumpTable) {
jt[STATICCALL].dynamicGas = gasStaticCallEIP7702 jt[STATICCALL].dynamicGas = gasStaticCallEIP7702
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702 jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702
} }
// opSlotNum enables the SLOTNUM opcode
func opSlotNum(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(uint256.NewInt(evm.Context.SlotNum))
return nil, nil
}
// enable7843 enables the SLOTNUM opcode as specified in EIP-7843.
func enable7843(jt *JumpTable) {
jt[SLOTNUM] = &operation{
execute: opSlotNum,
constantGas: GasQuickStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
}
}

View file

@ -66,6 +66,7 @@ type BlockContext struct {
BaseFee *big.Int // Provides information for BASEFEE (0 if vm runs with NoBaseFee flag and 0 gas price) BaseFee *big.Int // Provides information for BASEFEE (0 if vm runs with NoBaseFee flag and 0 gas price)
BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price) BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price)
Random *common.Hash // Provides information for PREVRANDAO Random *common.Hash // Provides information for PREVRANDAO
SlotNum uint64 // Provides information for SLOTNUM
} }
// TxContext provides the EVM with information about a transaction. // TxContext provides the EVM with information about a transaction.
@ -144,6 +145,8 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
evm.precompiles = activePrecompiledContracts(evm.chainRules) evm.precompiles = activePrecompiledContracts(evm.chainRules)
switch { switch {
case evm.chainRules.IsAmsterdam:
evm.table = &amsterdamInstructionSet
case evm.chainRules.IsOsaka: case evm.chainRules.IsOsaka:
evm.table = &osakaInstructionSet evm.table = &osakaInstructionSet
case evm.chainRules.IsVerkle: case evm.chainRules.IsVerkle:
@ -283,7 +286,11 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
evm.Context.Transfer(evm.StateDB, caller, addr, value) evm.Context.Transfer(evm.StateDB, caller, addr, value)
} }
if isPrecompile { if isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) var stateDB StateDB
if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB
}
ret, gas, err = RunPrecompiledContract(stateDB, p, addr, input, gas, evm.Config.Tracer)
} else { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // Initialise a new contract and set the code that is to be used by the EVM.
code := evm.resolveCode(addr) code := evm.resolveCode(addr)
@ -346,7 +353,11 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
// It is allowed to call precompiles, even via delegatecall // It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile { if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) var stateDB StateDB
if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB
}
ret, gas, err = RunPrecompiledContract(stateDB, p, addr, input, gas, evm.Config.Tracer)
} else { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only. // The contract is a scoped environment for this execution context only.
@ -389,7 +400,11 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
// It is allowed to call precompiles, even via delegatecall // It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile { if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) var stateDB StateDB
if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB
}
ret, gas, err = RunPrecompiledContract(stateDB, p, addr, input, gas, evm.Config.Tracer)
} else { } else {
// Initialise a new contract and make initialise the delegate values // Initialise a new contract and make initialise the delegate values
// //
@ -441,7 +456,11 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount) evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount)
if p, isPrecompile := evm.precompile(addr); isPrecompile { if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) var stateDB StateDB
if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB
}
ret, gas, err = RunPrecompiledContract(stateDB, p, addr, input, gas, evm.Config.Tracer)
} else { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only. // The contract is a scoped environment for this execution context only.
@ -578,8 +597,8 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
} }
// Check whether the max code size has been exceeded, assign err if the case. // Check whether the max code size has been exceeded, assign err if the case.
if evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
return ret, ErrMaxCodeSizeExceeded return ret, err
} }
// Reject code starting with 0xEF if EIP-3541 is enabled. // Reject code starting with 0xEF if EIP-3541 is enabled.

View file

@ -18,7 +18,6 @@ package vm
import ( import (
"errors" "errors"
"fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
@ -318,10 +317,10 @@ func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
if overflow { if overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if size > params.MaxInitCodeSize { if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil {
return 0, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size) return 0, err
} }
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow // Since size <= the protocol-defined maximum initcode size limit, these multiplication cannot overflow
moreGas := params.InitCodeWordGas * ((size + 31) / 32) moreGas := params.InitCodeWordGas * ((size + 31) / 32)
if gas, overflow = math.SafeAdd(gas, moreGas); overflow { if gas, overflow = math.SafeAdd(gas, moreGas); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
@ -337,10 +336,10 @@ func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
if overflow { if overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if size > params.MaxInitCodeSize { if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil {
return 0, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size) return 0, err
} }
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow // Since size <= the protocol-defined maximum initcode size limit, these multiplication cannot overflow
moreGas := (params.InitCodeWordGas + params.Keccak256WordGas) * ((size + 31) / 32) moreGas := (params.InitCodeWordGas + params.Keccak256WordGas) * ((size + 31) / 32)
if gas, overflow = math.SafeAdd(gas, moreGas); overflow { if gas, overflow = math.SafeAdd(gas, moreGas); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow

Some files were not shown because too many files have changed in this diff Show more