merge master branch

This commit is contained in:
maskpp 2025-06-03 22:08:32 +08:00
commit 1d7fbc0fbc
186 changed files with 12588 additions and 2929 deletions

View file

@ -0,0 +1,22 @@
on:
schedule:
- cron: '0 14 * * *'
workflow_dispatch:
jobs:
azure-cleanup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.24
cache: false
- name: Run cleanup script
run: |
go run build/ci.go purge -store gethstore/builds -days 14
env:
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}

View file

@ -0,0 +1,36 @@
on:
schedule:
- cron: '0 15 * * *'
push:
branches:
- "release/*"
workflow_dispatch:
jobs:
ppa:
name: PPA Upload
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.24
cache: false
- name: Install deb toolchain
run: |
apt-get update
apt-get -yq --no-install-suggests --no-install-recommends install devscripts debhelper dput fakeroot
- name: Add launchpad to known_hosts
run: |
echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
- name: Run ci.go
run: |
go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
env:
PPA_SIGNING_KEY: ${{ secrets.PPA_SIGNING_KEY }}
PPA_SSH_KEY: ${{ secrets.PPA_SSH_KEY }}

View file

@ -0,0 +1,147 @@
on:
push:
branches:
- "master"
tags:
- "v*"
jobs:
linux-intel:
name: Linux Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.24
cache: false
- name: Install cross toolchain
run: |
apt-get update
apt-get -yq --no-install-suggests --no-install-recommends install gcc-multilib
- name: Build (amd64)
run: |
go run build/ci.go install -arch amd64 -dlgo
- name: Create/upload archive (amd64)
run: |
go run build/ci.go archive -arch amd64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
rm -f build/bin/*
env:
LINUX_SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }}
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
- name: Build (386)
run: |
go run build/ci.go install -arch 386 -dlgo
- name: Create/upload archive (386)
run: |
go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
rm -f build/bin/*
env:
LINUX_SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }}
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
linux-arm:
name: Linux Build (arm)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.24
cache: false
- name: Install cross toolchain
run: |
apt-get update
apt-get -yq --no-install-suggests --no-install-recommends install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross
ln -s /usr/include/asm-generic /usr/include/asm
- name: Build (arm64)
run: |
go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc
- name: Create/upload archive (arm64)
run: |
go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
rm -fr build/bin/*
env:
LINUX_SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }}
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
- name: Run build (arm5)
run: |
go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc
env:
GOARM: "5"
- name: Create/upload archive (arm5)
run: |
go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
env:
GOARM: "5"
LINUX_SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }}
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
- name: Run build (arm6)
run: |
go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc
env:
GOARM: "6"
- name: Create/upload archive (arm6)
run: |
go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
rm -fr build/bin/*
env:
GOARM: "6"
LINUX_SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }}
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
- name: Run build (arm7)
run: |
go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc
env:
GOARM: "7"
- name: Create/upload archive (arm7)
run: |
go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
rm -fr build/bin/*
env:
GOARM: "7"
LINUX_SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }}
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
docker:
name: Docker Image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.24
cache: false
- name: Run docker build
env:
DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_PASSWORD }}
run: |
go run build/ci.go dockerx -platform linux/amd64,linux/arm64,linux/riscv64 -upload

View file

@ -50,6 +50,10 @@ Boqin Qin <bobbqqin@bupt.edu.cn> <Bobbqqin@gmail.com>
Casey Detrio <cdetrio@gmail.com> Casey Detrio <cdetrio@gmail.com>
Charlotte <tqpcharlie@proton.me>
Charlotte <tqpcharlie@proton.me> <Zachinquarantine@protonmail.com>
Charlotte <tqpcharlie@proton.me> <zachinquarantine@yahoo.com>
Cheng Li <lob4tt@gmail.com> Cheng Li <lob4tt@gmail.com>
Chris Ziogas <ziogaschr@gmail.com> Chris Ziogas <ziogaschr@gmail.com>
@ -301,9 +305,6 @@ Yohann Léon <sybiload@gmail.com>
yzb <335357057@qq.com> yzb <335357057@qq.com>
yzb <335357057@qq.com> <flyingyzb@gmail.com> yzb <335357057@qq.com> <flyingyzb@gmail.com>
Zachinquarantine <Zachinquarantine@protonmail.com>
Zachinquarantine <Zachinquarantine@protonmail.com> <zachinquarantine@yahoo.com>
Ziyuan Zhong <zzy.albert@163.com> Ziyuan Zhong <zzy.albert@163.com>
Zsolt Felföldi <zsfelfoldi@gmail.com> Zsolt Felföldi <zsfelfoldi@gmail.com>

View file

@ -5,7 +5,7 @@ jobs:
include: include:
# This builder create and push the Docker images for all architectures # This builder create and push the Docker images for all architectures
- stage: build - stage: build
if: type = push if: type = push && tag ~= /^v[0-9]/
os: linux os: linux
arch: amd64 arch: amd64
dist: focal dist: focal
@ -21,9 +21,25 @@ jobs:
script: script:
- go run build/ci.go dockerx -platform "linux/amd64,linux/arm64,linux/riscv64" -hub ethereum/client-go -upload - go run build/ci.go dockerx -platform "linux/amd64,linux/arm64,linux/riscv64" -hub ethereum/client-go -upload
# This builder does the Ubuntu PPA nightly uploads
- stage: build
if: type = push && tag ~= /^v[0-9]/
os: linux
dist: focal
go: 1.24.x
env:
- ubuntu-ppa
git:
submodules: false # avoid cloning ethereum/tests
before_install:
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends install devscripts debhelper dput fakeroot
script:
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
# This builder does the Linux Azure uploads # This builder does the Linux Azure uploads
- stage: build - stage: build
if: type = push if: type = push && tag ~= /^v[0-9]/
os: linux os: linux
dist: focal dist: focal
sudo: required sudo: required
@ -56,40 +72,6 @@ jobs:
- go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc - go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
# These builders run the tests
- stage: build
if: type = push
os: linux
arch: amd64
dist: focal
go: 1.24.x
script:
- travis_wait 45 go run build/ci.go test $TEST_PACKAGES
- stage: build
if: type = push
os: linux
dist: focal
go: 1.23.x
script:
- travis_wait 45 go run build/ci.go test $TEST_PACKAGES
# This builder does the Ubuntu PPA nightly uploads
- stage: build
if: type = cron || (type = push && tag ~= /^v[0-9]/)
os: linux
dist: focal
go: 1.24.x
env:
- ubuntu-ppa
git:
submodules: false # avoid cloning ethereum/tests
before_install:
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends install devscripts debhelper dput fakeroot
script:
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
# This builder does the Azure archive purges to avoid accumulating junk # This builder does the Azure archive purges to avoid accumulating junk
- stage: build - stage: build
if: type = cron if: type = cron
@ -102,14 +84,3 @@ jobs:
submodules: false # avoid cloning ethereum/tests submodules: false # avoid cloning ethereum/tests
script: script:
- go run build/ci.go purge -store gethstore/builds -days 14 - go run build/ci.go purge -store gethstore/builds -days 14
# This builder executes race tests
- stage: build
if: type = cron
os: linux
dist: focal
go: 1.24.x
env:
- racetests
script:
- travis_wait 60 go run build/ci.go test -race $TEST_PACKAGES

View file

@ -123,6 +123,7 @@ Ceyhun Onur <ceyhun.onur@avalabs.org>
chabashilah <doumodoumo@gmail.com> chabashilah <doumodoumo@gmail.com>
changhong <changhong.yu@shanbay.com> changhong <changhong.yu@shanbay.com>
Charles Cooper <cooper.charles.m@gmail.com> Charles Cooper <cooper.charles.m@gmail.com>
Charlotte <tqpcharlie@proton.me>
Chase Wright <mysticryuujin@gmail.com> Chase Wright <mysticryuujin@gmail.com>
Chawin Aiemvaravutigul <nick41746@hotmail.com> Chawin Aiemvaravutigul <nick41746@hotmail.com>
Chen Quan <terasum@163.com> Chen Quan <terasum@163.com>
@ -839,7 +840,6 @@ ywzqwwt <39263032+ywzqwwt@users.noreply.github.com>
yzb <335357057@qq.com> yzb <335357057@qq.com>
zaccoding <zaccoding725@gmail.com> zaccoding <zaccoding725@gmail.com>
Zach <zach.ramsay@gmail.com> Zach <zach.ramsay@gmail.com>
Zachinquarantine <Zachinquarantine@protonmail.com>
zah <zahary@gmail.com> zah <zahary@gmail.com>
Zahoor Mohamed <zahoor@zahoor.in> Zahoor Mohamed <zahoor@zahoor.in>
Zak Cole <zak@beattiecole.com> Zak Cole <zak@beattiecole.com>

View file

@ -162,7 +162,7 @@ accessible from the outside.
As a developer, sooner rather than later you'll want to start interacting with `geth` and the As a developer, sooner rather than later you'll want to start interacting with `geth` and the
Ethereum network via your own programs and not manually through the console. To aid Ethereum network via your own programs and not manually through the console. To aid
this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://ethereum.github.io/execution-apis/api-documentation/) this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://ethereum.org/en/developers/docs/apis/json-rpc/)
and [`geth` specific APIs](https://geth.ethereum.org/docs/interacting-with-geth/rpc)). and [`geth` specific APIs](https://geth.ethereum.org/docs/interacting-with-geth/rpc)).
These can be exposed via HTTP, WebSockets and IPC (UNIX sockets on UNIX based These can be exposed via HTTP, WebSockets and IPC (UNIX sockets on UNIX based
platforms, and named pipes on Windows). platforms, and named pipes on Windows).

View file

@ -1,116 +0,0 @@
// Code generated via abigen V2 - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package v1bindtests
import (
"bytes"
"errors"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind/v2"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
// Reference imports to suppress errors if they are not otherwise used.
var (
_ = bytes.Equal
_ = errors.New
_ = big.NewInt
_ = common.Big1
_ = types.BloomLookup
_ = abi.ConvertType
)
// Struct0 is an auto generated low-level Go binding around an user-defined struct.
type Struct0 struct {
B [32]byte
}
// StructsMetaData contains all meta data concerning the Structs contract.
var StructsMetaData = bind.MetaData{
ABI: "[{\"inputs\":[],\"name\":\"F\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"B\",\"type\":\"bytes32\"}],\"internalType\":\"structStructs.A[]\",\"name\":\"a\",\"type\":\"tuple[]\"},{\"internalType\":\"uint256[]\",\"name\":\"c\",\"type\":\"uint256[]\"},{\"internalType\":\"bool[]\",\"name\":\"d\",\"type\":\"bool[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"G\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"B\",\"type\":\"bytes32\"}],\"internalType\":\"structStructs.A[]\",\"name\":\"a\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]",
ID: "Structs",
}
// Structs is an auto generated Go binding around an Ethereum contract.
type Structs struct {
abi abi.ABI
}
// NewStructs creates a new instance of Structs.
func NewStructs() *Structs {
parsed, err := StructsMetaData.ParseABI()
if err != nil {
panic(errors.New("invalid ABI: " + err.Error()))
}
return &Structs{abi: *parsed}
}
// Instance creates a wrapper for a deployed contract instance at the given address.
<<<<<<< HEAD
// Use this to create the instance object passed to abigen v2 library functions Call, Transact, etc.
func (c *Structs) Instance(backend bind.ContractBackend, addr common.Address) *bind.BoundContract {
return bind.NewBoundContract(addr, c.abi, backend, backend, backend)
=======
// Use this to create the instance object passed to abigen v2 library functions Call,
// Transact, etc.
func (c *Structs) Instance(backend bind.ContractBackend, addr common.Address) bind.BoundContract {
return bind.NewBoundContract(backend, addr, c.abi)
>>>>>>> 854c25e086 (accounts/abi/abigen: improve v2 template)
}
// F is the Go binding used to pack the parameters required for calling
// the contract method 0x28811f59.
//
// Solidity: function F() view returns((bytes32)[] a, uint256[] c, bool[] d)
func (structs *Structs) PackF() ([]byte, error) {
return structs.abi.Pack("F")
}
// FOutput serves as a container for the return parameters of contract
// method F.
type FOutput struct {
A []Struct0
C []*big.Int
D []bool
}
// UnpackF is the Go binding that unpacks the parameters returned
// from invoking the contract method with ID 0x28811f59.
//
// Solidity: function F() view returns((bytes32)[] a, uint256[] c, bool[] d)
func (structs *Structs) UnpackF(data []byte) (*FOutput, error) {
out, err := structs.abi.Unpack("F", data)
if err != nil {
return nil, err
}
ret := new(FOutput)
ret.A = *abi.ConvertType(out[0], new([]Struct0)).(*[]Struct0)
ret.C = *abi.ConvertType(out[1], new([]*big.Int)).(*[]*big.Int)
ret.D = *abi.ConvertType(out[2], new([]bool)).(*[]bool)
return ret, nil
}
// G is the Go binding used to pack the parameters required for calling
// the contract method 0x6fecb623.
//
// Solidity: function G() view returns((bytes32)[] a)
func (structs *Structs) PackG() ([]byte, error) {
return structs.abi.Pack("G")
}
// UnpackG is the Go binding that unpacks the parameters returned
// from invoking the contract method with ID 0x6fecb623.
//
// Solidity: function G() view returns((bytes32)[] a)
func (structs *Structs) UnpackG(data []byte) (*[]Struct0, error) {
out, err := structs.abi.Unpack("G", data)
if err != nil {
return nil, err
}
out0 := *abi.ConvertType(out[0], new([]Struct0)).(*[]Struct0)
return &out0, nil
}

View file

@ -1,58 +1,58 @@
# This file contains sha256 checksums of optional build dependencies. # This file contains sha256 checksums of optional build dependencies.
# version:spec-tests pectra-devnet-6@v1.0.0 # version:spec-tests v4.5.0
# https://github.com/ethereum/execution-spec-tests/releases # https://github.com/ethereum/execution-spec-tests/releases
# https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-6%40v1.0.0/ # https://github.com/ethereum/execution-spec-tests/releases/download/v4.5.0/
b69211752a3029083c020dc635fe12156ca1a6725a08559da540a0337586a77e fixtures_pectra-devnet-6.tar.gz 58afb92a0075a2cb7c4dec1281f7cb88b21b02afbedad096b580f3f8cc14c54c fixtures_develop.tar.gz
# version:golang 1.24.2 # version:golang 1.24.3
# https://go.dev/dl/ # https://go.dev/dl/
9dc77ffadc16d837a1bf32d99c624cb4df0647cee7b119edd9e7b1bcc05f2e00 go1.24.2.src.tar.gz 229c08b600b1446798109fae1f569228102c8473caba8104b6418cb5bc032878 go1.24.3.src.tar.gz
427b373540d8fd51dbcc46bdecd340af109cd41514443c000d3dcde72b2c65a3 go1.24.2.aix-ppc64.tar.gz 6f6901497547db3b77c14f7f953fbcef9fa5fb84199ee2ee14a5686e66bed5a6 go1.24.3.aix-ppc64.tar.gz
238d9c065d09ff6af229d2e3b8b5e85e688318d69f4006fb85a96e41c216ea83 go1.24.2.darwin-amd64.tar.gz a05fa7e4043a4fec66897135219e3b8ab2202b5ef351c60c2fbb531dfb8f2900 go1.24.3.darwin-amd64.pkg
535ed9ff283fee39575a7fb9b6d8b1901b6dc640d06dc71fd7d3faeefdaf8030 go1.24.2.darwin-amd64.pkg 13e6fe3fcf65689d77d40e633de1e31c6febbdbcb846eb05fc2434ed2213e92b go1.24.3.darwin-amd64.tar.gz
b70f8b3c5b4ccb0ad4ffa5ee91cd38075df20fdbd953a1daedd47f50fbcff47a go1.24.2.darwin-arm64.tar.gz 97055ff4214043b39dc32e043fdd5c565df7c0a4e2fc0174e779a134c347ae0e go1.24.3.darwin-arm64.pkg
4732f607a47ce4d898c0af01ff68f07e0820a6b50603aef5d5c777d1102505e2 go1.24.2.darwin-arm64.pkg 64a3fa22142f627e78fac3018ce3d4aeace68b743eff0afda8aae0411df5e4fb go1.24.3.darwin-arm64.tar.gz
c17686b5fd61a663fbfafccfa177961be59386cf294e935ce35866b9dcb8e78a go1.24.2.dragonfly-amd64.tar.gz 32de3fd44d5055973978436a7f1f0ffbaae85c1b603ec6105e5c38d8a674c721 go1.24.3.dragonfly-amd64.tar.gz
026f1dd906189acff714c7625686bbc4ed91042618ba010d45b671461acc9e63 go1.24.2.freebsd-386.tar.gz 9fe6101b3797919bd7337ee5ce591954f85d59db7ae88983904db29fd64c3dd1 go1.24.3.freebsd-386.tar.gz
49399ba759b570a8f87d12179133403da6c2dd296d63a8830dee309161b9c40c go1.24.2.freebsd-amd64.tar.gz 6ccf4cca287e90cc28cd7954b6172f5d177a17e20b072b65f7f39636c325e2fb go1.24.3.freebsd-amd64.tar.gz
1f48f47183794d97c29736004247ab541177cf984ac6322c78bc43828daa1172 go1.24.2.freebsd-arm.tar.gz ce45ebf389066f82a7b056b66dd650efb51fde6f8bf92a2a3ab6990f02788ebf go1.24.3.freebsd-arm.tar.gz
ef856428b60a8c0bd9a2cba596e83024be6f1c2d5574e89cb1ff2262b08df8b9 go1.24.2.freebsd-arm64.tar.gz 8f6494a12a874d0ea57c67987829359e016960ce3ba0673273609d6ac2af589a go1.24.3.freebsd-arm64.tar.gz
ec2088823e16df00600a6d0f72e9a7dc6d2f80c9c140c2043c0cf20e1404d1a9 go1.24.2.freebsd-riscv64.tar.gz f9db392560cf0851f0bc8f2190e1978e01b4603038c27fecfc8658a695b71616 go1.24.3.freebsd-riscv64.tar.gz
e030e7cedbb8688f1d75cb80f3de6ee2e6617a67d34051e794e5992b53462147 go1.24.2.illumos-amd64.tar.gz 01717fff64c5d98457272002fa825d0a15e307bf6e189f2b0c23817fa033b61c go1.24.3.illumos-amd64.tar.gz
4c382776d52313266f3026236297a224a6688751256a2dffa3f524d8d6f6c0ba go1.24.2.linux-386.tar.gz 41b1051063e68cbd2b919bf12326764fe33937cf1d32b5c529dd1a4f43dce578 go1.24.3.linux-386.tar.gz
68097bd680839cbc9d464a0edce4f7c333975e27a90246890e9f1078c7e702ad go1.24.2.linux-amd64.tar.gz 3333f6ea53afa971e9078895eaa4ac7204a8c6b5c68c10e6bc9a33e8e391bdd8 go1.24.3.linux-amd64.tar.gz
756274ea4b68fa5535eb9fe2559889287d725a8da63c6aae4d5f23778c229f4b go1.24.2.linux-arm64.tar.gz a463cb59382bd7ae7d8f4c68846e73c4d589f223c589ac76871b66811ded7836 go1.24.3.linux-arm64.tar.gz
438d5d3d7dcb239b58d893a715672eabe670b9730b1fd1c8fc858a46722a598a go1.24.2.linux-armv6l.tar.gz 17a392d7e826625dd12a32099df0b00b85c32d8132ed86fe917183ee5c3f88ed go1.24.3.linux-armv6l.tar.gz
6aefd3bf59c3c5592eda4fb287322207f119c2210f3795afa9be48d3ccb73e1b go1.24.2.linux-loong64.tar.gz e4b003c04c902edc140153d279b42167f1ad7c229f48f1f729bbef5e65e88d1f go1.24.3.linux-loong64.tar.gz
93e49bb4692783b0e9a2deab9558c6e8d2867f35592aeff285adda60924167f3 go1.24.2.linux-mips.tar.gz 1c79d89edf835edf9d4336ccea7cb89bc5c0ca82b12b36b218d599a5400d60fe go1.24.3.linux-mips.tar.gz
6e86e703675016f3faf6604b8f68f20dc1bba75849136e6dd4f43f69c8a4a9d9 go1.24.2.linux-mips64.tar.gz 0b64fe147d69f4d681d8e8a035c760477531432f83d831f18d37cb9bf3652488 go1.24.3.linux-mips64.tar.gz
f233d237538ca1559a7d7cf519a29f0147923a951377bc4e467af4c059e68851 go1.24.2.linux-mips64le.tar.gz 396b784c255b64512dc00c302c053e43a3cbfc77518664c6ac5569aafad4d1e6 go1.24.3.linux-mips64le.tar.gz
545e1b9a7939f923fd53bde98334b987ef42eb353ee3e0bfede8aa06079d6b24 go1.24.2.linux-mipsle.tar.gz 93898313887f14e8efbe9d7386d5da4792b2d6c492bee562993fd4c9daa75c6d go1.24.3.linux-mipsle.tar.gz
6eab31481f2f46187bc1b6c887662eef06fc9d7271a8390854072cdb387c8d74 go1.24.2.linux-ppc64.tar.gz 873ae3a6a6655a7b6f820e095d9965507e8dfd3cf76bc92d75c564ecbca385f6 go1.24.3.linux-ppc64.tar.gz
5fff857791d541c71d8ea0171c73f6f99770d15ff7e2ad979104856d01f36563 go1.24.2.linux-ppc64le.tar.gz 341a749d168f47b1d4dad25e32cae70849b7ceed7c290823b853c9e6b0df0856 go1.24.3.linux-ppc64le.tar.gz
91bda1558fcbd1c92769ad86c8f5cf796f8c67b0d9d9c19f76eecfc75ce71527 go1.24.2.linux-riscv64.tar.gz fa482f53ccb4ba280316b8c5751ea67291507280d9166f2a38fe4d9b5d5fb64b go1.24.3.linux-riscv64.tar.gz
1cb3448166d6abb515a85a3ee5afbdf932081fb58ad7143a8fb666fbc06146d9 go1.24.2.linux-s390x.tar.gz a87b0c2a079a0bece1620fb29a00e02b4dba17507850f837e754af7d57cda282 go1.24.3.linux-s390x.tar.gz
a9a2c0db2e826f20f00b02bee01dfdaeb49591c2f6ffacb78dc64a950894f7ff go1.24.2.netbsd-386.tar.gz 63155382308db1306200aff7821aa26bf2a2dda23537dd637a9704b485b6ddf0 go1.24.3.netbsd-386.tar.gz
cd1a35b76ed9c7b6c0c1616741bd319699a77867ade0be9924f32496c0a87a3f go1.24.2.netbsd-amd64.tar.gz fe2c5c79482958b867c08a4fc2a10a998de9c0206b08d5b3ebcb2232e8d2777c go1.24.3.netbsd-amd64.tar.gz
8c666388d066e479155cc5116950eeb435df28087ef277c18f1dc7479f836e60 go1.24.2.netbsd-arm.tar.gz e8ff77aef21521b5dd94e44282a3243309b80717414cf12f72835a45886a049f go1.24.3.netbsd-arm.tar.gz
5d42f0be04f58da5be788a1e260f8747c316b8ce182bf0b273c2e4c691feaa1a go1.24.2.netbsd-arm64.tar.gz b337fbaf82822685940ffaa76fbcf4be5d2f0258bc819cd80bc408b491f45c04 go1.24.3.netbsd-arm64.tar.gz
688effa23ea3973cc8b0fdf4246712cbeef55ff20c45f3a9e28b0c2db04246cf go1.24.2.openbsd-386.tar.gz c1bb9dd8418480aa7f65452b08de3759da3bf89702be71b5a9fc084836b24ad5 go1.24.3.openbsd-386.tar.gz
e5daf95f1048d8026b1366450a3f8044d668b0639db6422ad9a83755c6745cf7 go1.24.2.openbsd-amd64.tar.gz 531218de748b0caaf6d1ad18921206fc12baaa89bf483a0a5e60a571c206fe6f go1.24.3.openbsd-amd64.tar.gz
aeadaf74bd544d1a12ba9b14c0e7cdb1964de3ba9a52acb4619e91dbae7def7b go1.24.2.openbsd-arm.tar.gz bcd0dc959986fc346969b5d4111c3c8031882d8bf8d87a2c2ecf1328962a91f2 go1.24.3.openbsd-arm.tar.gz
9e222d9adb0ce836a5b3c8d5aadbd167c8869c030b113f4a81aa88e9a200f279 go1.24.2.openbsd-arm64.tar.gz 00ee6f8f1c41fd2e28ad386bd7e39acce7cab84af6de835855b29d1c597335c4 go1.24.3.openbsd-arm64.tar.gz
192fffa34536adc3cd1bb7c1ee785b8bc156ae7afd10bbf5db99ec8f2e93066e go1.24.2.openbsd-ppc64.tar.gz 9f4ec0a9203ed3c54ce1a2a390ad3d45838cdb7efd85baeff857e37dfde04edd go1.24.3.openbsd-ppc64.tar.gz
a23e90b451a390549042c2a7efbec6f29ed98b2d5618c8d2a35704e21be96e09 go1.24.2.openbsd-riscv64.tar.gz da4d6f80e2373250d8c31c32dcd1e08775c327c0d610923604660cc0e07e8cba go1.24.3.openbsd-riscv64.tar.gz
5cdcafe455d859b02779611a5a1e1d63e498b922e05818fb3debe410a5959e9e go1.24.2.plan9-386.tar.gz f5d02149132eedda6c2d46b360d7da462b8a5f9e3f8567db100c2d7bff0ddcd7 go1.24.3.plan9-386.tar.gz
81351659804fa505c1b3ec6fdf9599f7f88df08614307eeb96071bf5e2e74beb go1.24.2.plan9-amd64.tar.gz 175f3d79f4762a3c545d2c6393bf6b8bac24e838026869dafab06b930735c94f go1.24.3.plan9-amd64.tar.gz
6e337d5def14ed0123423c1c32e2e6d8b19161e5d5ffaa7356dad48ee0fd80b4 go1.24.2.plan9-arm.tar.gz d1e4ac15095da1611659261c2228c2058756cf87d61d9fad262f76755ef26849 go1.24.3.plan9-arm.tar.gz
07e6926ebc476c044d7d5b17706abfc52be52bccc2073d1734174efe63c6b35e go1.24.2.solaris-amd64.tar.gz e644220a6ced3c07a7acc1364193cb709a97737dd8b6792a07a8ec6d9996713e go1.24.3.solaris-amd64.tar.gz
13d86cb818bba331da75fcd18246ab31a1067b44fb4a243b6dfd93097eda7f37 go1.24.2.windows-386.zip 0d7e7dc0a31ba0cdd487415709d03b02fc9490ef111e8dfd22788a6d63316f37 go1.24.3.windows-386.msi
8a702d9f7104a15bd935f4191c58c24c0b6389e066b9d5661b93915114a2bef0 go1.24.2.windows-386.msi c27c463a61ab849266baa0c17a6c5c4256a574ab642f609ba25c96ec965dc184 go1.24.3.windows-386.zip
29c553aabee0743e2ffa3e9fa0cda00ef3b3cc4ff0bc92007f31f80fd69892e1 go1.24.2.windows-amd64.zip d5b7637e7e138be877d96a4501709d480e050d86a8f402bc950e72112b5aedc5 go1.24.3.windows-amd64.msi
acefb191e72fea0bdb1a3f5f8f6f5ab18b42b3bbce0c7183f189f25953aff275 go1.24.2.windows-amd64.msi be9787cb08998b1860fe3513e48a5fe5b96302d358a321b58e651184fa9638b3 go1.24.3.windows-amd64.zip
ab267f7f9a3366d48d7664be9e627ce3e63273231430cce5f7783fb910f14148 go1.24.2.windows-arm64.zip 7efde2e5e8468e9caf2c7fc94f4da78a726a5031a1ed63acff7899527cdddff6 go1.24.3.windows-arm64.msi
d187bfe539356c39573d2f46766d1d08122b4f33da00fd14d12485fa9e241ff5 go1.24.2.windows-arm64.msi eec9fa736056b54dd88ecb669db2bfad39b0c48f6f9080f036dfa1ca42dc4bb5 go1.24.3.windows-arm64.zip
# version:golangci 2.0.2 # version:golangci 2.0.2
# https://github.com/golangci/golangci-lint/releases/ # https://github.com/golangci/golangci-lint/releases/

View file

@ -59,6 +59,7 @@ import (
"github.com/cespare/cp" "github.com/cespare/cp"
"github.com/ethereum/go-ethereum/crypto/signify" "github.com/ethereum/go-ethereum/crypto/signify"
"github.com/ethereum/go-ethereum/internal/build" "github.com/ethereum/go-ethereum/internal/build"
"github.com/ethereum/go-ethereum/internal/download"
"github.com/ethereum/go-ethereum/internal/version" "github.com/ethereum/go-ethereum/internal/version"
) )
@ -190,7 +191,7 @@ func doInstall(cmdline []string) {
// Configure the toolchain. // Configure the toolchain.
tc := build.GoToolchain{GOARCH: *arch, CC: *cc} tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
if *dlgo { if *dlgo {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := download.MustLoadChecksums("build/checksums.txt")
tc.Root = build.DownloadGo(csdb) tc.Root = build.DownloadGo(csdb)
} }
// Disable CLI markdown doc generation in release builds. // Disable CLI markdown doc generation in release builds.
@ -285,7 +286,7 @@ func doTest(cmdline []string) {
flag.CommandLine.Parse(cmdline) flag.CommandLine.Parse(cmdline)
// Get test fixtures. // Get test fixtures.
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := download.MustLoadChecksums("build/checksums.txt")
downloadSpecTestFixtures(csdb, *cachedir) downloadSpecTestFixtures(csdb, *cachedir)
// Configure the toolchain. // Configure the toolchain.
@ -329,16 +330,11 @@ func doTest(cmdline []string) {
} }
// downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures. // downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures.
func downloadSpecTestFixtures(csdb *build.ChecksumDB, cachedir string) string { func downloadSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string {
executionSpecTestsVersion, err := build.Version(csdb, "spec-tests")
if err != nil {
log.Fatal(err)
}
ext := ".tar.gz" ext := ".tar.gz"
base := "fixtures_pectra-devnet-6" // TODO(s1na) rename once the version becomes part of the filename base := "fixtures_develop"
url := fmt.Sprintf("https://github.com/ethereum/execution-spec-tests/releases/download/%s/%s%s", executionSpecTestsVersion, base, ext)
archivePath := filepath.Join(cachedir, base+ext) archivePath := filepath.Join(cachedir, base+ext)
if err := csdb.DownloadFile(url, archivePath); err != nil { if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := build.ExtractArchive(archivePath, executionSpecTestsDir); err != nil { if err := build.ExtractArchive(archivePath, executionSpecTestsDir); err != nil {
@ -444,14 +440,13 @@ func doLint(cmdline []string) {
// downloadLinter downloads and unpacks golangci-lint. // downloadLinter downloads and unpacks golangci-lint.
func downloadLinter(cachedir string) string { func downloadLinter(cachedir string) string {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := download.MustLoadChecksums("build/checksums.txt")
version, err := build.Version(csdb, "golangci") version, err := csdb.FindVersion("golangci")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
arch := runtime.GOARCH arch := runtime.GOARCH
ext := ".tar.gz" ext := ".tar.gz"
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
ext = ".zip" ext = ".zip"
} }
@ -459,9 +454,8 @@ func downloadLinter(cachedir string) string {
arch += "v" + os.Getenv("GOARM") arch += "v" + os.Getenv("GOARM")
} }
base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, arch) base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, arch)
url := fmt.Sprintf("https://github.com/golangci/golangci-lint/releases/download/v%s/%s%s", version, base, ext)
archivePath := filepath.Join(cachedir, base+ext) archivePath := filepath.Join(cachedir, base+ext)
if err := csdb.DownloadFile(url, archivePath); err != nil { if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := build.ExtractArchive(archivePath, cachedir); err != nil { if err := build.ExtractArchive(archivePath, cachedir); err != nil {
@ -497,8 +491,8 @@ func protocArchiveBaseName() (string, error) {
// in the generate command. It returns the full path of the directory // in the generate command. It returns the full path of the directory
// containing the 'protoc-gen-go' executable. // containing the 'protoc-gen-go' executable.
func downloadProtocGenGo(cachedir string) string { func downloadProtocGenGo(cachedir string) string {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := download.MustLoadChecksums("build/checksums.txt")
version, err := build.Version(csdb, "protoc-gen-go") version, err := csdb.FindVersion("protoc-gen-go")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -510,10 +504,8 @@ func downloadProtocGenGo(cachedir string) string {
archiveName += ".tar.gz" archiveName += ".tar.gz"
} }
url := fmt.Sprintf("https://github.com/protocolbuffers/protobuf-go/releases/download/v%s/%s", version, archiveName)
archivePath := path.Join(cachedir, archiveName) archivePath := path.Join(cachedir, archiveName)
if err := csdb.DownloadFile(url, archivePath); err != nil { if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
log.Fatal(err) log.Fatal(err)
} }
extractDest := filepath.Join(cachedir, baseName) extractDest := filepath.Join(cachedir, baseName)
@ -531,8 +523,8 @@ func downloadProtocGenGo(cachedir string) string {
// files as a CI step. It returns the full path to the directory containing // files as a CI step. It returns the full path to the directory containing
// the protoc executable. // the protoc executable.
func downloadProtoc(cachedir string) string { func downloadProtoc(cachedir string) string {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := download.MustLoadChecksums("build/checksums.txt")
version, err := build.Version(csdb, "protoc") version, err := csdb.FindVersion("protoc")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -543,10 +535,8 @@ func downloadProtoc(cachedir string) string {
fileName := fmt.Sprintf("protoc-%s-%s", version, baseName) fileName := fmt.Sprintf("protoc-%s-%s", version, baseName)
archiveFileName := fileName + ".zip" archiveFileName := fileName + ".zip"
url := fmt.Sprintf("https://github.com/protocolbuffers/protobuf/releases/download/v%s/%s", version, archiveFileName)
archivePath := filepath.Join(cachedir, archiveFileName) archivePath := filepath.Join(cachedir, archiveFileName)
if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
if err := csdb.DownloadFile(url, archivePath); err != nil {
log.Fatal(err) log.Fatal(err)
} }
extractDest := filepath.Join(cachedir, fileName) extractDest := filepath.Join(cachedir, fileName)
@ -826,18 +816,17 @@ func doDebianSource(cmdline []string) {
// downloadGoBootstrapSources downloads the Go source tarball(s) that will be used // downloadGoBootstrapSources downloads the Go source tarball(s) that will be used
// to bootstrap the builder Go. // to bootstrap the builder Go.
func downloadGoBootstrapSources(cachedir string) []string { func downloadGoBootstrapSources(cachedir string) []string {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := download.MustLoadChecksums("build/checksums.txt")
var bundles []string var bundles []string
for _, booter := range []string{"ppa-builder-1.19", "ppa-builder-1.21", "ppa-builder-1.23"} { for _, booter := range []string{"ppa-builder-1.19", "ppa-builder-1.21", "ppa-builder-1.23"} {
gobootVersion, err := build.Version(csdb, booter) gobootVersion, err := csdb.FindVersion(booter)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
file := fmt.Sprintf("go%s.src.tar.gz", gobootVersion) file := fmt.Sprintf("go%s.src.tar.gz", gobootVersion)
url := "https://dl.google.com/go/" + file
dst := filepath.Join(cachedir, file) dst := filepath.Join(cachedir, file)
if err := csdb.DownloadFile(url, dst); err != nil { if err := csdb.DownloadFileFromKnownURL(dst); err != nil {
log.Fatal(err) log.Fatal(err)
} }
bundles = append(bundles, dst) bundles = append(bundles, dst)
@ -847,15 +836,14 @@ func downloadGoBootstrapSources(cachedir string) []string {
// downloadGoSources downloads the Go source tarball. // downloadGoSources downloads the Go source tarball.
func downloadGoSources(cachedir string) string { func downloadGoSources(cachedir string) string {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := download.MustLoadChecksums("build/checksums.txt")
dlgoVersion, err := build.Version(csdb, "golang") dlgoVersion, err := csdb.FindVersion("golang")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion) file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion)
url := "https://dl.google.com/go/" + file
dst := filepath.Join(cachedir, file) dst := filepath.Join(cachedir, file)
if err := csdb.DownloadFile(url, dst); err != nil { if err := csdb.DownloadFileFromKnownURL(dst); err != nil {
log.Fatal(err) log.Fatal(err)
} }
return dst return dst
@ -1181,5 +1169,6 @@ func doPurge(cmdline []string) {
} }
func doSanityCheck() { func doSanityCheck() {
build.DownloadAndVerifyChecksums(build.MustLoadChecksums("build/checksums.txt")) csdb := download.MustLoadChecksums("build/checksums.txt")
csdb.DownloadAndVerifyAll()
} }

View file

@ -66,10 +66,9 @@ func (s *Suite) dialAs(key *ecdsa.PrivateKey) (*Conn, error) {
return nil, err return nil, err
} }
conn.caps = []p2p.Cap{ conn.caps = []p2p.Cap{
{Name: "eth", Version: 67}, {Name: "eth", Version: 69},
{Name: "eth", Version: 68},
} }
conn.ourHighestProtoVersion = 68 conn.ourHighestProtoVersion = 69
return &conn, nil return &conn, nil
} }
@ -156,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.StatusPacket) msg = new(eth.StatusPacket69)
case eth.GetBlockHeadersMsg: case eth.GetBlockHeadersMsg:
msg = new(eth.GetBlockHeadersPacket) msg = new(eth.GetBlockHeadersPacket)
case eth.BlockHeadersMsg: case eth.BlockHeadersMsg:
@ -229,9 +228,21 @@ func (c *Conn) ReadSnap() (any, error) {
} }
} }
// dialAndPeer creates a peer connection and runs the handshake.
func (s *Suite) dialAndPeer(status *eth.StatusPacket69) (*Conn, error) {
c, err := s.dial()
if err != nil {
return nil, err
}
if err = c.peer(s.chain, status); err != nil {
c.Close()
}
return c, err
}
// 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.StatusPacket) error { func (c *Conn) peer(chain *Chain, status *eth.StatusPacket69) 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)
} }
@ -304,7 +315,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.StatusPacket) error { func (c *Conn) statusExchange(chain *Chain, status *eth.StatusPacket69) error {
loop: loop:
for { for {
code, data, err := c.Read() code, data, err := c.Read()
@ -313,12 +324,16 @@ loop:
} }
switch code { switch code {
case eth.StatusMsg + protoOffset(ethProto): case eth.StatusMsg + protoOffset(ethProto):
msg := new(eth.StatusPacket) msg := new(eth.StatusPacket69)
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)
} }
if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want { if have, want := msg.LatestBlock, chain.blocks[chain.Len()-1].NumberU64(); have != want {
return fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x", return fmt.Errorf("wrong head block in status, want: %d, have %d",
want, have)
}
if have, want := msg.LatestBlockHash, chain.blocks[chain.Len()-1].Hash(); have != want {
return fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x",
want, chain.blocks[chain.Len()-1].NumberU64(), have) want, chain.blocks[chain.Len()-1].NumberU64(), have)
} }
if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) { if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) {
@ -348,13 +363,14 @@ loop:
} }
if status == nil { if status == nil {
// default status message // default status message
status = &eth.StatusPacket{ status = &eth.StatusPacket69{
ProtocolVersion: uint32(c.negotiatedProtoVersion), ProtocolVersion: uint32(c.negotiatedProtoVersion),
NetworkID: chain.config.ChainID.Uint64(), NetworkID: chain.config.ChainID.Uint64(),
TD: chain.TD(),
Head: chain.blocks[chain.Len()-1].Hash(),
Genesis: chain.blocks[0].Hash(), Genesis: chain.blocks[0].Hash(),
ForkID: chain.ForkID(), ForkID: chain.ForkID(),
EarliestBlock: 0,
LatestBlock: chain.blocks[chain.Len()-1].NumberU64(),
LatestBlockHash: chain.blocks[chain.Len()-1].Hash(),
} }
} }
if err := c.Write(ethProto, eth.StatusMsg, status); err != nil { if err := c.Write(ethProto, eth.StatusMsg, status); err != nil {

View file

@ -32,7 +32,7 @@ const (
// Unexported devp2p protocol lengths from p2p package. // Unexported devp2p protocol lengths from p2p package.
const ( const (
baseProtoLen = 16 baseProtoLen = 16
ethProtoLen = 17 ethProtoLen = 18
snapProtoLen = 8 snapProtoLen = 8
) )

View file

@ -68,16 +68,19 @@ func (s *Suite) EthTests() []utesting.Test {
return []utesting.Test{ return []utesting.Test{
// status // status
{Name: "Status", Fn: s.TestStatus}, {Name: "Status", Fn: s.TestStatus},
{Name: "MaliciousHandshake", Fn: s.TestMaliciousHandshake},
{Name: "BlockRangeUpdateExpired", Fn: s.TestBlockRangeUpdateHistoryExp},
{Name: "BlockRangeUpdateFuture", Fn: s.TestBlockRangeUpdateFuture},
{Name: "BlockRangeUpdateInvalid", Fn: s.TestBlockRangeUpdateInvalid},
// get block headers // get block headers
{Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders}, {Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders},
{Name: "GetNonexistentBlockHeaders", Fn: s.TestGetNonexistentBlockHeaders}, {Name: "GetNonexistentBlockHeaders", Fn: s.TestGetNonexistentBlockHeaders},
{Name: "SimultaneousRequests", Fn: s.TestSimultaneousRequests}, {Name: "SimultaneousRequests", Fn: s.TestSimultaneousRequests},
{Name: "SameRequestID", Fn: s.TestSameRequestID}, {Name: "SameRequestID", Fn: s.TestSameRequestID},
{Name: "ZeroRequestID", Fn: s.TestZeroRequestID}, {Name: "ZeroRequestID", Fn: s.TestZeroRequestID},
// get block bodies // get history
{Name: "GetBlockBodies", Fn: s.TestGetBlockBodies}, {Name: "GetBlockBodies", Fn: s.TestGetBlockBodies},
// // malicious handshakes + status {Name: "GetReceipts", Fn: s.TestGetReceipts},
{Name: "MaliciousHandshake", Fn: s.TestMaliciousHandshake},
// test transactions // test transactions
{Name: "LargeTxRequest", Fn: s.TestLargeTxRequest, Slow: true}, {Name: "LargeTxRequest", Fn: s.TestLargeTxRequest, Slow: true},
{Name: "Transaction", Fn: s.TestTransaction}, {Name: "Transaction", Fn: s.TestTransaction},
@ -101,15 +104,11 @@ func (s *Suite) SnapTests() []utesting.Test {
func (s *Suite) TestStatus(t *utesting.T) { func (s *Suite) TestStatus(t *utesting.T) {
t.Log(`This test is just a sanity check. It performs an eth protocol handshake.`) t.Log(`This test is just a sanity check. It performs an eth protocol handshake.`)
conn, err := s.dialAndPeer(nil)
conn, err := s.dial()
if err != nil { if err != nil {
t.Fatalf("dial failed: %v", err) t.Fatal("peering failed:", err)
}
defer conn.Close()
if err := conn.peer(s.chain, nil); err != nil {
t.Fatalf("peering failed: %v", err)
} }
conn.Close()
} }
// headersMatch returns whether the received headers match the given request // headersMatch returns whether the received headers match the given request
@ -119,15 +118,12 @@ func headersMatch(expected []*types.Header, headers []*types.Header) bool {
func (s *Suite) TestGetBlockHeaders(t *utesting.T) { func (s *Suite) TestGetBlockHeaders(t *utesting.T) {
t.Log(`This test requests block headers from the node.`) t.Log(`This test requests block headers from the node.`)
conn, err := s.dialAndPeer(nil)
conn, err := s.dial()
if err != nil { if err != nil {
t.Fatalf("dial failed: %v", err)
}
defer conn.Close()
if err = conn.peer(s.chain, nil); err != nil {
t.Fatalf("peering failed: %v", err) t.Fatalf("peering failed: %v", err)
} }
defer conn.Close()
// Send headers request. // Send headers request.
req := &eth.GetBlockHeadersPacket{ req := &eth.GetBlockHeadersPacket{
RequestId: 33, RequestId: 33,
@ -160,18 +156,13 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) {
} }
func (s *Suite) TestGetNonexistentBlockHeaders(t *utesting.T) { func (s *Suite) TestGetNonexistentBlockHeaders(t *utesting.T) {
t.Log(`This test sends GetBlockHeaders requests for nonexistent blocks (using max uint64 value) t.Log(`This test sends GetBlockHeaders requests for nonexistent blocks (using max uint64 value)
to check if the node disconnects after receiving multiple invalid requests.`) to check if the node disconnects after receiving multiple invalid requests.`)
conn, err := s.dialAndPeer(nil)
conn, err := s.dial()
if err != nil { if err != nil {
t.Fatalf("dial failed: %v", err)
}
defer conn.Close()
if err := conn.peer(s.chain, nil); err != nil {
t.Fatalf("peering failed: %v", err) t.Fatalf("peering failed: %v", err)
} }
defer conn.Close()
// Create request with max uint64 value for a nonexistent block // Create request with max uint64 value for a nonexistent block
badReq := &eth.GetBlockHeadersPacket{ badReq := &eth.GetBlockHeadersPacket{
@ -204,15 +195,11 @@ to check if the node disconnects after receiving multiple invalid requests.`)
func (s *Suite) TestSimultaneousRequests(t *utesting.T) { func (s *Suite) TestSimultaneousRequests(t *utesting.T) {
t.Log(`This test requests blocks headers from the node, performing two requests t.Log(`This test requests blocks headers from the node, performing two requests
concurrently, with different request IDs.`) concurrently, with different request IDs.`)
conn, err := s.dialAndPeer(nil)
conn, err := s.dial()
if err != nil { if err != nil {
t.Fatalf("dial failed: %v", err)
}
defer conn.Close()
if err := conn.peer(s.chain, nil); err != nil {
t.Fatalf("peering failed: %v", err) t.Fatalf("peering failed: %v", err)
} }
defer conn.Close()
// Create two different requests. // Create two different requests.
req1 := &eth.GetBlockHeadersPacket{ req1 := &eth.GetBlockHeadersPacket{
@ -278,15 +265,11 @@ concurrently, with different request IDs.`)
func (s *Suite) TestSameRequestID(t *utesting.T) { func (s *Suite) TestSameRequestID(t *utesting.T) {
t.Log(`This test requests block headers, performing two concurrent requests with the t.Log(`This test requests block headers, performing two concurrent requests with the
same request ID. The node should handle the request by responding to both requests.`) same request ID. The node should handle the request by responding to both requests.`)
conn, err := s.dialAndPeer(nil)
conn, err := s.dial()
if err != nil { if err != nil {
t.Fatalf("dial failed: %v", err)
}
defer conn.Close()
if err := conn.peer(s.chain, nil); err != nil {
t.Fatalf("peering failed: %v", err) t.Fatalf("peering failed: %v", err)
} }
defer conn.Close()
// Create two different requests with the same ID. // Create two different requests with the same ID.
reqID := uint64(1234) reqID := uint64(1234)
@ -349,15 +332,12 @@ same request ID. The node should handle the request by responding to both reques
func (s *Suite) TestZeroRequestID(t *utesting.T) { func (s *Suite) TestZeroRequestID(t *utesting.T) {
t.Log(`This test sends a GetBlockHeaders message with a request-id of zero, t.Log(`This test sends a GetBlockHeaders message with a request-id of zero,
and expects a response.`) and expects a response.`)
conn, err := s.dialAndPeer(nil)
conn, err := s.dial()
if err != nil { if err != nil {
t.Fatalf("dial failed: %v", err)
}
defer conn.Close()
if err := conn.peer(s.chain, nil); err != nil {
t.Fatalf("peering failed: %v", err) t.Fatalf("peering failed: %v", err)
} }
defer conn.Close()
req := &eth.GetBlockHeadersPacket{ req := &eth.GetBlockHeadersPacket{
GetBlockHeadersRequest: &eth.GetBlockHeadersRequest{ GetBlockHeadersRequest: &eth.GetBlockHeadersRequest{
Origin: eth.HashOrNumber{Number: 0}, Origin: eth.HashOrNumber{Number: 0},
@ -384,15 +364,12 @@ and expects a response.`)
func (s *Suite) TestGetBlockBodies(t *utesting.T) { func (s *Suite) TestGetBlockBodies(t *utesting.T) {
t.Log(`This test sends GetBlockBodies requests to the node for known blocks in the test chain.`) t.Log(`This test sends GetBlockBodies requests to the node for known blocks in the test chain.`)
conn, err := s.dialAndPeer(nil)
conn, err := s.dial()
if err != nil { if err != nil {
t.Fatalf("dial failed: %v", err)
}
defer conn.Close()
if err := conn.peer(s.chain, nil); err != nil {
t.Fatalf("peering failed: %v", err) t.Fatalf("peering failed: %v", err)
} }
defer conn.Close()
// Create block bodies request. // Create block bodies request.
req := &eth.GetBlockBodiesPacket{ req := &eth.GetBlockBodiesPacket{
RequestId: 55, RequestId: 55,
@ -418,6 +395,47 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) {
} }
} }
func (s *Suite) TestGetReceipts(t *utesting.T) {
t.Log(`This test sends GetReceipts requests to the node for known blocks in the test chain.`)
conn, err := s.dialAndPeer(nil)
if err != nil {
t.Fatalf("peering failed: %v", err)
}
defer conn.Close()
// Find some blocks containing receipts.
var hashes = make([]common.Hash, 0, 3)
for i := range s.chain.Len() {
block := s.chain.GetBlock(i)
if len(block.Transactions()) > 0 {
hashes = append(hashes, block.Hash())
}
if len(hashes) == cap(hashes) {
break
}
}
// Create block bodies request.
req := &eth.GetReceiptsPacket{
RequestId: 66,
GetReceiptsRequest: (eth.GetReceiptsRequest)(hashes),
}
if err := conn.Write(ethProto, eth.GetReceiptsMsg, req); err != nil {
t.Fatalf("could not write to connection: %v", err)
}
// Wait for response.
resp := new(eth.ReceiptsPacket[*eth.ReceiptList69])
if err := conn.ReadMsg(ethProto, eth.ReceiptsMsg, &resp); err != nil {
t.Fatalf("error reading block bodies msg: %v", err)
}
if got, want := resp.RequestId, req.RequestId; got != want {
t.Fatalf("unexpected request id in respond", got, want)
}
if len(resp.List) != len(req.GetReceiptsRequest) {
t.Fatalf("wrong bodies in response: expected %d bodies, got %d", len(req.GetReceiptsRequest), len(resp.List))
}
}
// randBuf makes a random buffer size kilobytes large. // randBuf makes a random buffer size kilobytes large.
func randBuf(size int) []byte { func randBuf(size int) []byte {
buf := make([]byte, size*1024) buf := make([]byte, size*1024)
@ -500,6 +518,97 @@ func (s *Suite) TestMaliciousHandshake(t *utesting.T) {
} }
} }
func (s *Suite) TestBlockRangeUpdateInvalid(t *utesting.T) {
t.Log(`This test sends an invalid BlockRangeUpdate message to the node and expects to be disconnected.`)
conn, err := s.dialAndPeer(nil)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
conn.Write(ethProto, eth.BlockRangeUpdateMsg, &eth.BlockRangeUpdatePacket{
EarliestBlock: 10,
LatestBlock: 8,
LatestBlockHash: s.chain.GetBlock(8).Hash(),
})
if code, _, err := conn.Read(); err != nil {
t.Fatalf("expected disconnect, got err: %v", err)
} else if code != discMsg {
t.Fatalf("expected disconnect message, got msg code %d", code)
}
}
func (s *Suite) TestBlockRangeUpdateFuture(t *utesting.T) {
t.Log(`This test sends a BlockRangeUpdate that is beyond the chain head.
The node should accept the update and should not disonnect.`)
conn, err := s.dialAndPeer(nil)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
head := s.chain.Head().NumberU64()
var hash common.Hash
rand.Read(hash[:])
conn.Write(ethProto, eth.BlockRangeUpdateMsg, &eth.BlockRangeUpdatePacket{
EarliestBlock: head + 10,
LatestBlock: head + 50,
LatestBlockHash: hash,
})
// Ensure the node does not disconnect us.
// Just send a few ping messages.
for range 10 {
time.Sleep(100 * time.Millisecond)
if err := conn.Write(baseProto, pingMsg, []any{}); err != nil {
t.Fatal("write error:", err)
}
code, _, err := conn.Read()
switch {
case err != nil:
t.Fatal("read error:", err)
case code == discMsg:
t.Fatal("got disconnect")
case code == pongMsg:
}
}
}
func (s *Suite) TestBlockRangeUpdateHistoryExp(t *utesting.T) {
t.Log(`This test sends a BlockRangeUpdate announcing incomplete (expired) history.
The node should accept the update and should not disonnect.`)
conn, err := s.dialAndPeer(nil)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
head := s.chain.Head()
conn.Write(ethProto, eth.BlockRangeUpdateMsg, &eth.BlockRangeUpdatePacket{
EarliestBlock: head.NumberU64() - 10,
LatestBlock: head.NumberU64(),
LatestBlockHash: head.Hash(),
})
// Ensure the node does not disconnect us.
// Just send a few ping messages.
for range 10 {
time.Sleep(100 * time.Millisecond)
if err := conn.Write(baseProto, pingMsg, []any{}); err != nil {
t.Fatal("write error:", err)
}
code, _, err := conn.Read()
switch {
case err != nil:
t.Fatal("read error:", err)
case code == discMsg:
t.Fatal("got disconnect")
case code == pongMsg:
}
}
}
func (s *Suite) TestTransaction(t *utesting.T) { func (s *Suite) TestTransaction(t *utesting.T) {
t.Log(`This test sends a valid transaction to the node and checks if the t.Log(`This test sends a valid transaction to the node and checks if the
transaction gets propagated.`) transaction gets propagated.`)

View file

@ -21,9 +21,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path/filepath"
"regexp"
"runtime" "runtime"
"slices" "slices"
"strconv" "strconv"
"strings"
"sync/atomic" "sync/atomic"
"time" "time"
@ -39,8 +42,10 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/era" "github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/internal/era/eradl"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -190,7 +195,7 @@ This command dumps out the state for a given block (or latest, if none provided)
`, `,
} }
pruneCommand = &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 the merge block",
@ -201,6 +206,42 @@ 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 the merge block, 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.`,
} }
downloadEraCommand = &cli.Command{
Action: downloadEra,
Name: "download-era",
Usage: "Fetches era1 files (pre-merge history) from an HTTP endpoint",
ArgsUsage: "",
Flags: slices.Concat(
utils.DatabaseFlags,
utils.NetworkFlags,
[]cli.Flag{
eraBlockFlag,
eraEpochFlag,
eraAllFlag,
eraServerFlag,
},
),
}
)
var (
eraBlockFlag = &cli.StringFlag{
Name: "block",
Usage: "Block number to fetch. (can also be a range <start>-<end>)",
}
eraEpochFlag = &cli.StringFlag{
Name: "epoch",
Usage: "Epoch number to fetch (can also be a range <start>-<end>)",
}
eraAllFlag = &cli.BoolFlag{
Name: "all",
Usage: "Download all available era1 files",
}
eraServerFlag = &cli.StringFlag{
Name: "server",
Usage: "era1 server URL",
}
) )
// initGenesis will initialise the given JSON format genesis file and writes it as // initGenesis will initialise the given JSON format genesis file and writes it as
@ -237,10 +278,7 @@ func initGenesis(ctx *cli.Context) error {
overrides.OverrideVerkle = &v overrides.OverrideVerkle = &v
} }
chaindb, err := stack.OpenDatabaseWithFreezer("chaindata", 0, 0, ctx.String(utils.AncientFlag.Name), "", false) chaindb := utils.MakeChainDatabase(ctx, stack, false)
if err != nil {
utils.Fatalf("Failed to open database: %v", err)
}
defer chaindb.Close() defer chaindb.Close()
triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle()) triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle())
@ -277,7 +315,7 @@ func dumpGenesis(ctx *cli.Context) error {
// dump whatever already exists in the datadir // dump whatever already exists in the datadir
stack, _ := makeConfigNode(ctx) stack, _ := makeConfigNode(ctx)
db, err := stack.OpenDatabase("chaindata", 0, 0, "", true) db, err := stack.OpenDatabaseWithOptions("chaindata", node.DatabaseOptions{ReadOnly: true})
if err != nil { if err != nil {
return err return err
} }
@ -665,3 +703,83 @@ func pruneHistory(ctx *cli.Context) error {
return nil return nil
} }
// downladEra is the era1 file downloader tool.
func downloadEra(ctx *cli.Context) error {
flags.CheckExclusive(ctx, eraBlockFlag, eraEpochFlag, eraAllFlag)
// Resolve the network.
var network = "mainnet"
if utils.IsNetworkPreset(ctx) {
switch {
case ctx.IsSet(utils.MainnetFlag.Name):
case ctx.IsSet(utils.SepoliaFlag.Name):
network = "sepolia"
default:
return fmt.Errorf("unsupported network, no known era1 checksums")
}
}
// Resolve the destination directory.
stack, _ := makeConfigNode(ctx)
defer stack.Close()
ancients := stack.ResolveAncient("chaindata", "")
dir := filepath.Join(ancients, "era")
baseURL := ctx.String(eraServerFlag.Name)
if baseURL == "" {
return fmt.Errorf("need --%s flag to download", eraServerFlag.Name)
}
l, err := eradl.New(baseURL, network)
if err != nil {
return err
}
switch {
case ctx.IsSet(eraAllFlag.Name):
return l.DownloadAll(dir)
case ctx.IsSet(eraBlockFlag.Name):
s := ctx.String(eraBlockFlag.Name)
start, end, ok := parseRange(s)
if !ok {
return fmt.Errorf("invalid block range: %q", s)
}
return l.DownloadBlockRange(start, end, dir)
case ctx.IsSet(eraEpochFlag.Name):
s := ctx.String(eraEpochFlag.Name)
start, end, ok := parseRange(s)
if !ok {
return fmt.Errorf("invalid epoch range: %q", s)
}
return l.DownloadEpochRange(start, end, dir)
default:
return fmt.Errorf("specify one of --%s, --%s, or --%s to download", eraAllFlag.Name, eraBlockFlag.Name, eraEpochFlag.Name)
}
}
func parseRange(s string) (start uint64, end uint64, ok bool) {
if m, _ := regexp.MatchString("[0-9]+", s); m {
start, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, 0, false
}
end = start
return start, end, true
}
if m, _ := regexp.MatchString("[0-9]+-[0-9]+", s); m {
s1, s2, _ := strings.Cut(s, "-")
start, err := strconv.ParseUint(s1, 10, 64)
if err != nil {
return 0, 0, false
}
end, err = strconv.ParseUint(s2, 10, 64)
if err != nil {
return 0, 0, false
}
return start, end, true
}
return 0, 0, false
}

View file

@ -36,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"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/crypto"
"github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
@ -180,6 +181,45 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
return stack, cfg return stack, cfg
} }
// constructs the disclaimer text block which will be printed in the logs upon
// startup when Geth is running in dev mode.
func constructDevModeBanner(ctx *cli.Context, cfg gethConfig) string {
devModeBanner := `You are running Geth in --dev mode. Please note the following:
1. This mode is only intended for fast, iterative development without assumptions on
security or persistence.
2. The database is created in memory unless specified otherwise. Therefore, shutting down
your computer or losing power will wipe your entire block data and chain state for
your dev environment.
3. A random, pre-allocated developer account will be available and unlocked as
eth.coinbase, which can be used for testing. The random dev account is temporary,
stored on a ramdisk, and will be lost if your machine is restarted.
4. Mining is enabled by default. However, the client will only seal blocks if transactions
are pending in the mempool. The miner's minimum accepted gas price is 1.
5. Networking is disabled; there is no listen-address, the maximum number of peers is set
to 0, and discovery is disabled.
`
if !ctx.IsSet(utils.DataDirFlag.Name) {
devModeBanner += fmt.Sprintf(`
Running in ephemeral mode. The following account has been prefunded in the genesis:
Account
------------------
0x%x (10^49 ETH)
`, cfg.Eth.Miner.PendingFeeRecipient)
if cfg.Eth.Miner.PendingFeeRecipient == utils.DeveloperAddr {
devModeBanner += fmt.Sprintf(`
Private Key
------------------
0x%x
`, crypto.FromECDSA(utils.DeveloperKey))
}
}
return devModeBanner
}
// makeFullNode loads geth configuration and creates the Ethereum backend. // makeFullNode loads geth configuration and creates the Ethereum backend.
func makeFullNode(ctx *cli.Context) *node.Node { func makeFullNode(ctx *cli.Context) *node.Node {
stack, cfg := makeConfigNode(ctx) stack, cfg := makeConfigNode(ctx)
@ -239,6 +279,11 @@ func makeFullNode(ctx *cli.Context) *node.Node {
} }
catalyst.RegisterSimulatedBeaconAPIs(stack, simBeacon) catalyst.RegisterSimulatedBeaconAPIs(stack, simBeacon)
stack.RegisterLifecycle(simBeacon) stack.RegisterLifecycle(simBeacon)
banner := constructDevModeBanner(ctx, cfg)
for _, line := range strings.Split(banner, "\n") {
log.Warn(line)
}
} else if ctx.IsSet(utils.BeaconApiFlag.Name) { } else if ctx.IsSet(utils.BeaconApiFlag.Name) {
// Start blsync mode. // Start blsync mode.
srv := rpc.NewServer() srv := rpc.NewServer()

View file

@ -102,17 +102,17 @@ func TestAttachWelcome(t *testing.T) {
"--http", "--http.port", httpPort, "--http", "--http.port", httpPort,
"--ws", "--ws.port", wsPort) "--ws", "--ws.port", wsPort)
t.Run("ipc", func(t *testing.T) { t.Run("ipc", func(t *testing.T) {
waitForEndpoint(t, ipc, 4*time.Second) waitForEndpoint(t, ipc, 2*time.Minute)
testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs) testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs)
}) })
t.Run("http", func(t *testing.T) { t.Run("http", func(t *testing.T) {
endpoint := "http://127.0.0.1:" + httpPort endpoint := "http://127.0.0.1:" + httpPort
waitForEndpoint(t, endpoint, 4*time.Second) waitForEndpoint(t, endpoint, 2*time.Minute)
testAttachWelcome(t, geth, endpoint, httpAPIs) testAttachWelcome(t, geth, endpoint, httpAPIs)
}) })
t.Run("ws", func(t *testing.T) { t.Run("ws", func(t *testing.T) {
endpoint := "ws://127.0.0.1:" + wsPort endpoint := "ws://127.0.0.1:" + wsPort
waitForEndpoint(t, endpoint, 4*time.Second) waitForEndpoint(t, endpoint, 2*time.Minute)
testAttachWelcome(t, geth, endpoint, httpAPIs) testAttachWelcome(t, geth, endpoint, httpAPIs)
}) })
geth.Kill() geth.Kill()

View file

@ -91,13 +91,7 @@ var (
utils.LogNoHistoryFlag, utils.LogNoHistoryFlag,
utils.LogExportCheckpointsFlag, utils.LogExportCheckpointsFlag,
utils.StateHistoryFlag, utils.StateHistoryFlag,
utils.LightServeFlag, // deprecated
utils.LightIngressFlag, // deprecated
utils.LightEgressFlag, // deprecated
utils.LightMaxPeersFlag, // deprecated
utils.LightNoPruneFlag, // deprecated
utils.LightKDFFlag, utils.LightKDFFlag,
utils.LightNoSyncServeFlag, // deprecated
utils.EthRequiredBlocksFlag, utils.EthRequiredBlocksFlag,
utils.LegacyWhitelistFlag, // deprecated utils.LegacyWhitelistFlag, // deprecated
utils.CacheFlag, utils.CacheFlag,
@ -225,7 +219,8 @@ func init() {
removedbCommand, removedbCommand,
dumpCommand, dumpCommand,
dumpGenesisCommand, dumpGenesisCommand,
pruneCommand, pruneHistoryCommand,
downloadEraCommand,
// See accountcmd.go: // See accountcmd.go:
accountCommand, accountCommand,
walletCommand, walletCommand,
@ -299,24 +294,6 @@ func prepare(ctx *cli.Context) {
case ctx.IsSet(utils.HoodiFlag.Name): case ctx.IsSet(utils.HoodiFlag.Name):
log.Info("Starting Geth on Hoodi testnet...") log.Info("Starting Geth on Hoodi testnet...")
case ctx.IsSet(utils.DeveloperFlag.Name):
log.Info("Starting Geth in ephemeral dev mode...")
log.Warn(`You are running Geth in --dev mode. Please note the following:
1. This mode is only intended for fast, iterative development without assumptions on
security or persistence.
2. The database is created in memory unless specified otherwise. Therefore, shutting down
your computer or losing power will wipe your entire block data and chain state for
your dev environment.
3. A random, pre-allocated developer account will be available and unlocked as
eth.coinbase, which can be used for testing. The random dev account is temporary,
stored on a ramdisk, and will be lost if your machine is restarted.
4. Mining is enabled by default. However, the client will only seal blocks if transactions
are pending in the mempool. The miner's minimum accepted gas price is 1.
5. Networking is disabled; there is no listen-address, the maximum number of peers is set
to 0, and discovery is disabled.
`)
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...")
} }

View file

@ -309,7 +309,8 @@ func ImportHistory(chain *core.BlockChain, dir string, network string) error {
if err != nil { if err != nil {
return fmt.Errorf("error reading receipts %d: %w", it.Number(), err) return fmt.Errorf("error reading receipts %d: %w", it.Number(), err)
} }
if _, err := chain.InsertReceiptChain([]*types.Block{block}, []types.Receipts{receipts}, 2^64-1); err != nil { encReceipts := types.EncodeBlockReceiptLists([]types.Receipts{receipts})
if _, err := chain.InsertReceiptChain([]*types.Block{block}, encReceipts, 2^64-1); err != nil {
return fmt.Errorf("error inserting body %d: %w", it.Number(), err) return fmt.Errorf("error inserting body %d: %w", it.Number(), err)
} }
imported += 1 imported += 1

View file

@ -111,6 +111,11 @@ var (
Usage: "Root directory for ancient data (default = inside chaindata)", Usage: "Root directory for ancient data (default = inside chaindata)",
Category: flags.EthCategory, Category: flags.EthCategory,
} }
EraFlag = &flags.DirectoryFlag{
Name: "datadir.era",
Usage: "Root directory for era1 history (default = inside ancient/chain)",
Category: flags.EthCategory,
}
MinFreeDiskSpaceFlag = &flags.DirectoryFlag{ MinFreeDiskSpaceFlag = &flags.DirectoryFlag{
Name: "datadir.minfreedisk", Name: "datadir.minfreedisk",
Usage: "Minimum free disk space in MB, once reached triggers auto shut down (default = --cache.gc converted to MB, 0 = disabled)", Usage: "Minimum free disk space in MB, once reached triggers auto shut down (default = --cache.gc converted to MB, 0 = disabled)",
@ -977,6 +982,7 @@ var (
DatabaseFlags = []cli.Flag{ DatabaseFlags = []cli.Flag{
DataDirFlag, DataDirFlag,
AncientFlag, AncientFlag,
EraFlag,
RemoteDBFlag, RemoteDBFlag,
DBEngineFlag, DBEngineFlag,
StateSchemeFlag, StateSchemeFlag,
@ -984,6 +990,12 @@ var (
} }
) )
// default account to prefund when running Geth in dev mode
var (
DeveloperKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
DeveloperAddr = crypto.PubkeyToAddress(DeveloperKey.PublicKey)
)
// MakeDataDir retrieves the currently requested data directory, terminating // MakeDataDir retrieves the currently requested data directory, terminating
// if none (or the empty string) is specified. If the node is starting a testnet, // if none (or the empty string) is specified. If the node is starting a testnet,
// then a subdirectory of the specified datadir will be used. // then a subdirectory of the specified datadir will be used.
@ -1245,28 +1257,6 @@ func setIPC(ctx *cli.Context, cfg *node.Config) {
} }
} }
// setLes shows the deprecation warnings for LES flags.
func setLes(ctx *cli.Context, cfg *ethconfig.Config) {
if ctx.IsSet(LightServeFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightServeFlag.Name)
}
if ctx.IsSet(LightIngressFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightIngressFlag.Name)
}
if ctx.IsSet(LightEgressFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightEgressFlag.Name)
}
if ctx.IsSet(LightMaxPeersFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightMaxPeersFlag.Name)
}
if ctx.IsSet(LightNoPruneFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightNoPruneFlag.Name)
}
if ctx.IsSet(LightNoSyncServeFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightNoSyncServeFlag.Name)
}
}
// MakeDatabaseHandles raises out the number of allowed file handles per process // MakeDatabaseHandles raises out the number of allowed file handles per process
// for Geth and returns half of the allowance to assign to the database. // for Geth and returns half of the allowance to assign to the database.
func MakeDatabaseHandles(max int) int { func MakeDatabaseHandles(max int) int {
@ -1582,7 +1572,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
setBlobPool(ctx, &cfg.BlobPool) setBlobPool(ctx, &cfg.BlobPool)
setMiner(ctx, &cfg.Miner) setMiner(ctx, &cfg.Miner)
setRequiredBlocks(ctx, cfg) setRequiredBlocks(ctx, cfg)
setLes(ctx, cfg)
// Cap the cache allowance and tune the garbage collector // Cap the cache allowance and tune the garbage collector
mem, err := gopsutil.VirtualMemory() mem, err := gopsutil.VirtualMemory()
@ -1630,6 +1619,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(AncientFlag.Name) { if ctx.IsSet(AncientFlag.Name) {
cfg.DatabaseFreezer = ctx.String(AncientFlag.Name) cfg.DatabaseFreezer = ctx.String(AncientFlag.Name)
} }
if ctx.IsSet(EraFlag.Name) {
cfg.DatabaseEra = ctx.String(EraFlag.Name)
}
if gcmode := ctx.String(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" { if gcmode := ctx.String(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" {
Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name) Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name)
@ -1712,7 +1704,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
} }
} }
if ctx.IsSet(VMEnableDebugFlag.Name) { if ctx.IsSet(VMEnableDebugFlag.Name) {
// TODO(fjl): force-enable this in --dev mode
cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name) cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name)
} }
@ -1761,6 +1752,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
case ctx.Bool(DeveloperFlag.Name): case ctx.Bool(DeveloperFlag.Name):
cfg.NetworkId = 1337 cfg.NetworkId = 1337
cfg.SyncMode = ethconfig.FullSync cfg.SyncMode = ethconfig.FullSync
cfg.EnablePreimageRecording = true
// Create new developer account or reuse existing one // Create new developer account or reuse existing one
var ( var (
developer accounts.Account developer accounts.Account
@ -1792,9 +1784,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
} else if accs := ks.Accounts(); len(accs) > 0 { } else if accs := ks.Accounts(); len(accs) > 0 {
developer = ks.Accounts()[0] developer = ks.Accounts()[0]
} else { } else {
developer, err = ks.NewAccount(passphrase) developer, err = ks.ImportECDSA(DeveloperKey, passphrase)
if err != nil { if err != nil {
Fatalf("Failed to create developer account: %v", err) Fatalf("Failed to import developer account: %v", err)
} }
} }
// Make sure the address is configured as fee recipient, otherwise // Make sure the address is configured as fee recipient, otherwise
@ -1809,14 +1801,18 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
} }
log.Info("Using developer account", "address", developer.Address) log.Info("Using developer account", "address", developer.Address)
// Create a new developer genesis block or reuse existing one // configure default developer genesis which will be used unless a
// datadir is specified and a chain is preexisting at that location.
cfg.Genesis = core.DeveloperGenesisBlock(ctx.Uint64(DeveloperGasLimitFlag.Name), &developer.Address) cfg.Genesis = core.DeveloperGenesisBlock(ctx.Uint64(DeveloperGasLimitFlag.Name), &developer.Address)
// If a datadir is specified, ensure that any preexisting chain in that location
// has a configuration that is compatible with dev mode: it must be merged at genesis.
if ctx.IsSet(DataDirFlag.Name) { if ctx.IsSet(DataDirFlag.Name) {
chaindb := tryMakeReadOnlyDatabase(ctx, stack) chaindb := tryMakeReadOnlyDatabase(ctx, stack)
if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) { if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) {
cfg.Genesis = nil // fallback to db content // signal fallback to preexisting chain on disk
cfg.Genesis = nil
//validate genesis has PoS enabled in block 0
genesis, err := core.ReadGenesis(chaindb) genesis, err := core.ReadGenesis(chaindb)
if err != nil { if err != nil {
Fatalf("Could not read genesis from database: %v", err) Fatalf("Could not read genesis from database: %v", err)
@ -2043,7 +2039,6 @@ func SetupMetrics(cfg *metrics.Config) {
log.Info("Enabling metrics export to InfluxDB") log.Info("Enabling metrics export to InfluxDB")
go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "geth.", tagsMap) go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "geth.", tagsMap)
} else if enableExportV2 { } else if enableExportV2 {
tagsMap := SplitTagsFlag(cfg.InfluxDBTags)
log.Info("Enabling metrics export to InfluxDB (v2)") log.Info("Enabling metrics export to InfluxDB (v2)")
go influxdb.InfluxDBV2WithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, token, bucket, organization, "geth.", tagsMap) go influxdb.InfluxDBV2WithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, token, bucket, organization, "geth.", tagsMap)
} }
@ -2096,7 +2091,15 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb.
} }
chainDb = remotedb.New(client) chainDb = remotedb.New(client)
default: default:
chainDb, err = stack.OpenDatabaseWithFreezer("chaindata", cache, handles, ctx.String(AncientFlag.Name), "eth/db/chaindata/", readonly) options := node.DatabaseOptions{
ReadOnly: readonly,
Cache: cache,
Handles: handles,
AncientsDirectory: ctx.String(AncientFlag.Name),
MetricsNamespace: "eth/db/chaindata/",
EraDirectory: ctx.String(EraFlag.Name),
}
chainDb, err = stack.OpenDatabaseWithOptions("chaindata", options)
} }
if err != nil { if err != nil {
Fatalf("Could not open database: %v", err) Fatalf("Could not open database: %v", err)

View file

@ -39,12 +39,6 @@ var DeprecatedFlags = []cli.Flag{
CacheTrieRejournalFlag, CacheTrieRejournalFlag,
LegacyDiscoveryV5Flag, LegacyDiscoveryV5Flag,
TxLookupLimitFlag, TxLookupLimitFlag,
LightServeFlag,
LightIngressFlag,
LightEgressFlag,
LightMaxPeersFlag,
LightNoPruneFlag,
LightNoSyncServeFlag,
LogBacktraceAtFlag, LogBacktraceAtFlag,
LogDebugFlag, LogDebugFlag,
MinerNewPayloadTimeoutFlag, MinerNewPayloadTimeoutFlag,
@ -88,37 +82,6 @@ var (
Value: ethconfig.Defaults.TransactionHistory, Value: ethconfig.Defaults.TransactionHistory,
Category: flags.DeprecatedCategory, Category: flags.DeprecatedCategory,
} }
// Light server and client settings, Deprecated November 2023
LightServeFlag = &cli.IntFlag{
Name: "light.serve",
Usage: "Maximum percentage of time allowed for serving LES requests (deprecated)",
Category: flags.DeprecatedCategory,
}
LightIngressFlag = &cli.IntFlag{
Name: "light.ingress",
Usage: "Incoming bandwidth limit for serving light clients (deprecated)",
Category: flags.DeprecatedCategory,
}
LightEgressFlag = &cli.IntFlag{
Name: "light.egress",
Usage: "Outgoing bandwidth limit for serving light clients (deprecated)",
Category: flags.DeprecatedCategory,
}
LightMaxPeersFlag = &cli.IntFlag{
Name: "light.maxpeers",
Usage: "Maximum number of light clients to serve, or light servers to attach to (deprecated)",
Category: flags.DeprecatedCategory,
}
LightNoPruneFlag = &cli.BoolFlag{
Name: "light.nopruning",
Usage: "Disable ancient light chain data pruning (deprecated)",
Category: flags.DeprecatedCategory,
}
LightNoSyncServeFlag = &cli.BoolFlag{
Name: "light.nosyncserve",
Usage: "Enables serving light clients before syncing (deprecated)",
Category: flags.DeprecatedCategory,
}
// Deprecated November 2023 // Deprecated November 2023
LogBacktraceAtFlag = &cli.StringFlag{ LogBacktraceAtFlag = &cli.StringFlag{
Name: "log.backtrace", Name: "log.backtrace",

View file

@ -158,7 +158,7 @@ func TestHistoryImportAndExport(t *testing.T) {
} }
// Now import Era. // Now import Era.
db2, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) db2, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -47,30 +47,37 @@ func NewBasicLRU[K comparable, V any](capacity int) BasicLRU[K, V] {
// Add adds a value to the cache. Returns true if an item was evicted to store the new item. // Add adds a value to the cache. Returns true if an item was evicted to store the new item.
func (c *BasicLRU[K, V]) Add(key K, value V) (evicted bool) { func (c *BasicLRU[K, V]) Add(key K, value V) (evicted bool) {
_, _, evicted = c.Add3(key, value)
return evicted
}
// Add3 adds a value to the cache. If an item was evicted to store the new one, it returns the evicted item.
func (c *BasicLRU[K, V]) Add3(key K, value V) (ek K, ev V, evicted bool) {
item, ok := c.items[key] item, ok := c.items[key]
if ok { if ok {
// Already exists in cache.
item.value = value item.value = value
c.items[key] = item c.items[key] = item
c.list.moveToFront(item.elem) c.list.moveToFront(item.elem)
return false return ek, ev, false
} }
var elem *listElem[K] var elem *listElem[K]
if c.Len() >= c.cap { if c.Len() >= c.cap {
elem = c.list.removeLast() elem = c.list.removeLast()
delete(c.items, elem.v)
evicted = true evicted = true
ek = elem.v
ev = c.items[ek].value
delete(c.items, ek)
} else { } else {
elem = new(listElem[K]) elem = new(listElem[K])
} }
// Store the new item. // Store the new item.
// Note that, if another item was evicted, we re-use its list element here. // Note that if another item was evicted, we re-use its list element here.
elem.v = key elem.v = key
c.items[key] = cacheItem[K, V]{elem, value} c.items[key] = cacheItem[K, V]{elem, value}
c.list.pushElem(elem) c.list.pushElem(elem)
return evicted return ek, ev, evicted
} }
// Contains reports whether the given key exists in the cache. // Contains reports whether the given key exists in the cache.

View file

@ -30,7 +30,6 @@ import (
"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/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -445,11 +444,6 @@ func (beacon *Beacon) CalcDifficulty(chain consensus.ChainHeaderReader, time uin
return beaconDifficulty return beaconDifficulty
} }
// APIs implements consensus.Engine, returning the user facing RPC APIs.
func (beacon *Beacon) APIs(chain consensus.ChainHeaderReader) []rpc.API {
return beacon.ethone.APIs(chain)
}
// Close shutdowns the consensus engine // Close shutdowns the consensus engine
func (beacon *Beacon) Close() error { func (beacon *Beacon) Close() error {
return beacon.ethone.Close() return beacon.ethone.Close()

View file

@ -1,235 +0,0 @@
// Copyright 2017 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 clique
import (
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
)
// API is a user facing RPC API to allow controlling the signer and voting
// mechanisms of the proof-of-authority scheme.
type API struct {
chain consensus.ChainHeaderReader
clique *Clique
}
// GetSnapshot retrieves the state snapshot at a given block.
func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) {
// Retrieve the requested block number (or current if none requested)
var header *types.Header
if number == nil || *number == rpc.LatestBlockNumber {
header = api.chain.CurrentHeader()
} else {
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
}
// Ensure we have an actually valid block and return its snapshot
if header == nil {
return nil, errUnknownBlock
}
return api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
}
// GetSnapshotAtHash retrieves the state snapshot at a given block.
func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error) {
header := api.chain.GetHeaderByHash(hash)
if header == nil {
return nil, errUnknownBlock
}
return api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
}
// GetSigners retrieves the list of authorized signers at the specified block.
func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error) {
// Retrieve the requested block number (or current if none requested)
var header *types.Header
if number == nil || *number == rpc.LatestBlockNumber {
header = api.chain.CurrentHeader()
} else {
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
}
// Ensure we have an actually valid block and return the signers from its snapshot
if header == nil {
return nil, errUnknownBlock
}
snap, err := api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
if err != nil {
return nil, err
}
return snap.signers(), nil
}
// GetSignersAtHash retrieves the list of authorized signers at the specified block.
func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) {
header := api.chain.GetHeaderByHash(hash)
if header == nil {
return nil, errUnknownBlock
}
snap, err := api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
if err != nil {
return nil, err
}
return snap.signers(), nil
}
// Proposals returns the current proposals the node tries to uphold and vote on.
func (api *API) Proposals() map[common.Address]bool {
api.clique.lock.RLock()
defer api.clique.lock.RUnlock()
proposals := make(map[common.Address]bool)
for address, auth := range api.clique.proposals {
proposals[address] = auth
}
return proposals
}
// Propose injects a new authorization proposal that the signer will attempt to
// push through.
func (api *API) Propose(address common.Address, auth bool) {
api.clique.lock.Lock()
defer api.clique.lock.Unlock()
api.clique.proposals[address] = auth
}
// Discard drops a currently running proposal, stopping the signer from casting
// further votes (either for or against).
func (api *API) Discard(address common.Address) {
api.clique.lock.Lock()
defer api.clique.lock.Unlock()
delete(api.clique.proposals, address)
}
type status struct {
InturnPercent float64 `json:"inturnPercent"`
SigningStatus map[common.Address]int `json:"sealerActivity"`
NumBlocks uint64 `json:"numBlocks"`
}
// Status returns the status of the last N blocks,
// - the number of active signers,
// - the number of signers,
// - the percentage of in-turn blocks
func (api *API) Status() (*status, error) {
var (
numBlocks = uint64(64)
header = api.chain.CurrentHeader()
diff = uint64(0)
optimals = 0
)
snap, err := api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
if err != nil {
return nil, err
}
var (
signers = snap.signers()
end = header.Number.Uint64()
start = end - numBlocks
)
if numBlocks > end {
start = 1
numBlocks = end - start
}
signStatus := make(map[common.Address]int)
for _, s := range signers {
signStatus[s] = 0
}
for n := start; n < end; n++ {
h := api.chain.GetHeaderByNumber(n)
if h == nil {
return nil, fmt.Errorf("missing block %d", n)
}
if h.Difficulty.Cmp(diffInTurn) == 0 {
optimals++
}
diff += h.Difficulty.Uint64()
sealer, err := api.clique.Author(h)
if err != nil {
return nil, err
}
signStatus[sealer]++
}
return &status{
InturnPercent: float64(100*optimals) / float64(numBlocks),
SigningStatus: signStatus,
NumBlocks: numBlocks,
}, nil
}
type blockNumberOrHashOrRLP struct {
*rpc.BlockNumberOrHash
RLP hexutil.Bytes `json:"rlp,omitempty"`
}
func (sb *blockNumberOrHashOrRLP) UnmarshalJSON(data []byte) error {
bnOrHash := new(rpc.BlockNumberOrHash)
// Try to unmarshal bNrOrHash
if err := bnOrHash.UnmarshalJSON(data); err == nil {
sb.BlockNumberOrHash = bnOrHash
return nil
}
// Try to unmarshal RLP
var input string
if err := json.Unmarshal(data, &input); err != nil {
return err
}
blob, err := hexutil.Decode(input)
if err != nil {
return err
}
sb.RLP = blob
return nil
}
// GetSigner returns the signer for a specific clique block.
// Can be called with a block number, a block hash or a rlp encoded blob.
// The RLP encoded blob can either be a block or a header.
func (api *API) GetSigner(rlpOrBlockNr *blockNumberOrHashOrRLP) (common.Address, error) {
if len(rlpOrBlockNr.RLP) == 0 {
blockNrOrHash := rlpOrBlockNr.BlockNumberOrHash
var header *types.Header
if blockNrOrHash == nil {
header = api.chain.CurrentHeader()
} else if hash, ok := blockNrOrHash.Hash(); ok {
header = api.chain.GetHeaderByHash(hash)
} else if number, ok := blockNrOrHash.Number(); ok {
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
}
if header == nil {
return common.Address{}, fmt.Errorf("missing block %v", blockNrOrHash.String())
}
return api.clique.Author(header)
}
block := new(types.Block)
if err := rlp.DecodeBytes(rlpOrBlockNr.RLP, block); err == nil {
return api.clique.Author(block.Header())
}
header := new(types.Header)
if err := rlp.DecodeBytes(rlpOrBlockNr.RLP, header); err != nil {
return common.Address{}, err
}
return api.clique.Author(header)
}

View file

@ -41,7 +41,6 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"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/rpc"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
) )
@ -641,15 +640,6 @@ func (c *Clique) Close() error {
return nil return nil
} }
// APIs implements consensus.Engine, returning the user facing RPC API to allow
// controlling the signer voting.
func (c *Clique) APIs(chain consensus.ChainHeaderReader) []rpc.API {
return []rpc.API{{
Namespace: "clique",
Service: &API{chain: chain, clique: c},
}}
}
// SealHash returns the hash of a block prior to it being sealed. // SealHash returns the hash of a block prior to it being sealed.
func SealHash(header *types.Header) (hash common.Hash) { func SealHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewLegacyKeccak256() hasher := sha3.NewLegacyKeccak256()

View file

@ -25,7 +25,6 @@ import (
"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/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
) )
// ChainHeaderReader defines a small collection of methods needed to access the local // ChainHeaderReader defines a small collection of methods needed to access the local
@ -109,9 +108,6 @@ type Engine interface {
// that a new block should have. // that a new block should have.
CalcDifficulty(chain ChainHeaderReader, time uint64, parent *types.Header) *big.Int CalcDifficulty(chain ChainHeaderReader, time uint64, parent *types.Header) *big.Int
// APIs returns the RPC APIs this consensus engine provides.
APIs(chain ChainHeaderReader) []rpc.API
// Close terminates any background threads maintained by the consensus engine. // Close terminates any background threads maintained by the consensus engine.
Close() error Close() error
} }

View file

@ -22,7 +22,6 @@ import (
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"
) )
// Ethash is a consensus engine based on proof-of-work implementing the ethash // Ethash is a consensus engine based on proof-of-work implementing the ethash
@ -71,12 +70,6 @@ func (ethash *Ethash) Close() error {
return nil return nil
} }
// APIs implements consensus.Engine, returning no APIs as ethash is an empty
// shell in the post-merge world.
func (ethash *Ethash) APIs(chain consensus.ChainHeaderReader) []rpc.API {
return []rpc.API{}
}
// Seal generates a new sealing request for the given input block and pushes // Seal generates a new sealing request for the given input block and pushes
// the result into the given channel. For the ethash engine, this method will // the result into the given channel. For the ethash engine, this method will
// just panic as sealing is not supported anymore. // just panic as sealing is not supported anymore.

View file

@ -65,7 +65,8 @@ var (
headFinalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil) headFinalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil)
headSafeBlockGauge = metrics.NewRegisteredGauge("chain/head/safe", nil) headSafeBlockGauge = metrics.NewRegisteredGauge("chain/head/safe", nil)
chainInfoGauge = metrics.NewRegisteredGaugeInfo("chain/info", nil) chainInfoGauge = metrics.NewRegisteredGaugeInfo("chain/info", nil)
chainMgaspsGauge = metrics.NewRegisteredGauge("chain/mgasps", nil)
accountReadTimer = metrics.NewRegisteredResettingTimer("chain/account/reads", nil) accountReadTimer = metrics.NewRegisteredResettingTimer("chain/account/reads", nil)
accountHashTimer = metrics.NewRegisteredResettingTimer("chain/account/hashes", nil) accountHashTimer = metrics.NewRegisteredResettingTimer("chain/account/hashes", nil)
@ -92,8 +93,10 @@ var (
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil) blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil) blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil)
blockPrefetchExecuteTimer = metrics.NewRegisteredTimer("chain/prefetch/executes", nil) blockPrefetchExecuteTimer = metrics.NewRegisteredResettingTimer("chain/prefetch/executes", nil)
blockPrefetchInterruptMeter = metrics.NewRegisteredMeter("chain/prefetch/interrupts", nil) blockPrefetchInterruptMeter = metrics.NewRegisteredMeter("chain/prefetch/interrupts", nil)
blockPrefetchTxsInvalidMeter = metrics.NewRegisteredMeter("chain/prefetch/txs/invalid", nil)
blockPrefetchTxsValidMeter = metrics.NewRegisteredMeter("chain/prefetch/txs/valid", nil)
errInsertionInterrupted = errors.New("insertion is interrupted") errInsertionInterrupted = errors.New("insertion is interrupted")
errChainStopped = errors.New("blockchain is stopped") errChainStopped = errors.New("blockchain is stopped")
@ -180,8 +183,13 @@ func (c *CacheConfig) triedbConfig(isVerkle bool) *triedb.Config {
} }
if c.StateScheme == rawdb.PathScheme { if c.StateScheme == rawdb.PathScheme {
config.PathDB = &pathdb.Config{ config.PathDB = &pathdb.Config{
StateHistory: c.StateHistory, StateHistory: c.StateHistory,
CleanCacheSize: c.TrieCleanLimit * 1024 * 1024, TrieCleanSize: c.TrieCleanLimit * 1024 * 1024,
StateCleanSize: c.SnapshotLimit * 1024 * 1024,
// TODO(rjl493456442): The write buffer represents the memory limit used
// for flushing both trie data and state data to disk. The config name
// should be updated to eliminate the confusion.
WriteBufferSize: c.TrieDirtyLimit * 1024 * 1024, WriteBufferSize: c.TrieDirtyLimit * 1024 * 1024,
} }
} }
@ -377,11 +385,14 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
// Do nothing here until the state syncer picks it up. // Do nothing here until the state syncer picks it up.
log.Info("Genesis state is missing, wait state sync") log.Info("Genesis state is missing, wait state sync")
} else { } else {
// Head state is missing, before the state recovery, find out the // Head state is missing, before the state recovery, find out the disk
// disk layer point of snapshot(if it's enabled). Make sure the // layer point of snapshot(if it's enabled). Make sure the rewound point
// rewound point is lower than disk layer. // is lower than disk layer.
//
// Note it's unnecessary in path mode which always keep trie data and
// state data consistent.
var diskRoot common.Hash var diskRoot common.Hash
if bc.cacheConfig.SnapshotLimit > 0 { if bc.cacheConfig.SnapshotLimit > 0 && bc.cacheConfig.StateScheme == rawdb.HashScheme {
diskRoot = rawdb.ReadSnapshotRoot(bc.db) diskRoot = rawdb.ReadSnapshotRoot(bc.db)
} }
if diskRoot != (common.Hash{}) { if diskRoot != (common.Hash{}) {
@ -454,7 +465,32 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
bc.logger.OnGenesisBlock(bc.genesisBlock, alloc) bc.logger.OnGenesisBlock(bc.genesisBlock, alloc)
} }
} }
bc.setupSnapshot()
// Rewind the chain in case of an incompatible config upgrade.
if compatErr != nil {
log.Warn("Rewinding chain to upgrade configuration", "err", compatErr)
if compatErr.RewindToTime > 0 {
bc.SetHeadWithTimestamp(compatErr.RewindToTime)
} else {
bc.SetHead(compatErr.RewindToBlock)
}
rawdb.WriteChainConfig(db, genesisHash, chainConfig)
}
// Start tx indexer if it's enabled.
if txLookupLimit != nil {
bc.txIndexer = newTxIndexer(*txLookupLimit, bc)
}
return bc, nil
}
func (bc *BlockChain) setupSnapshot() {
// Short circuit if the chain is established with path scheme, as the
// state snapshot has been integrated into path database natively.
if bc.cacheConfig.StateScheme == rawdb.PathScheme {
return
}
// Load any existing snapshot, regenerating it if loading failed // Load any existing snapshot, regenerating it if loading failed
if bc.cacheConfig.SnapshotLimit > 0 { if bc.cacheConfig.SnapshotLimit > 0 {
// If the chain was rewound past the snapshot persistent layer (causing // If the chain was rewound past the snapshot persistent layer (causing
@ -462,7 +498,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
// in recovery mode and in that case, don't invalidate the snapshot on a // in recovery mode and in that case, don't invalidate the snapshot on a
// head mismatch. // head mismatch.
var recover bool var recover bool
head := bc.CurrentBlock() head := bc.CurrentBlock()
if layer := rawdb.ReadSnapshotRecoveryNumber(bc.db); layer != nil && *layer >= head.Number.Uint64() { if layer := rawdb.ReadSnapshotRecoveryNumber(bc.db); layer != nil && *layer >= head.Number.Uint64() {
log.Warn("Enabling snapshot recovery", "chainhead", head.Number, "diskbase", *layer) log.Warn("Enabling snapshot recovery", "chainhead", head.Number, "diskbase", *layer)
@ -479,22 +514,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
// Re-initialize the state database with snapshot // Re-initialize the state database with snapshot
bc.statedb = state.NewDatabase(bc.triedb, bc.snaps) bc.statedb = state.NewDatabase(bc.triedb, bc.snaps)
} }
// Rewind the chain in case of an incompatible config upgrade.
if compatErr != nil {
log.Warn("Rewinding chain to upgrade configuration", "err", compatErr)
if compatErr.RewindToTime > 0 {
bc.SetHeadWithTimestamp(compatErr.RewindToTime)
} else {
bc.SetHead(compatErr.RewindToBlock)
}
rawdb.WriteChainConfig(db, genesisHash, chainConfig)
}
// Start tx indexer if it's enabled.
if txLookupLimit != nil {
bc.txIndexer = newTxIndexer(*txLookupLimit, bc)
}
return bc, nil
} }
// empty returns an indicator whether the blockchain is empty. // empty returns an indicator whether the blockchain is empty.
@ -1290,12 +1309,11 @@ const (
// //
// The optional ancientLimit can also be specified and chain segment before that // The optional ancientLimit can also be specified and chain segment before that
// will be directly stored in the ancient, getting rid of the chain migration. // will be directly stored in the ancient, getting rid of the chain migration.
func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts, ancientLimit uint64) (int, error) { func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []rlp.RawValue, ancientLimit uint64) (int, error) {
// Verify the supplied headers before insertion without lock // Verify the supplied headers before insertion without lock
var headers []*types.Header var headers []*types.Header
for _, block := range blockChain { for _, block := range blockChain {
headers = append(headers, block.Header()) headers = append(headers, block.Header())
// Here we also validate that blob transactions in the block do not // Here we also validate that blob transactions in the block do not
// contain a sidecar. While the sidecar does not affect the block hash // contain a sidecar. While the sidecar does not affect the block hash
// or tx hash, sending blobs within a block is not allowed. // or tx hash, sending blobs within a block is not allowed.
@ -1338,11 +1356,11 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
// //
// this function only accepts canonical chain data. All side chain will be reverted // this function only accepts canonical chain data. All side chain will be reverted
// eventually. // eventually.
writeAncient := func(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) { writeAncient := func(blockChain types.Blocks, receiptChain []rlp.RawValue) (int, error) {
// Ensure genesis is in the ancient store // Ensure genesis is in the ancient store
if blockChain[0].NumberU64() == 1 { if blockChain[0].NumberU64() == 1 {
if frozen, _ := bc.db.Ancients(); frozen == 0 { if frozen, _ := bc.db.Ancients(); frozen == 0 {
writeSize, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []types.Receipts{nil}) writeSize, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []rlp.RawValue{rlp.EmptyList})
if err != nil { if err != nil {
log.Error("Error writing genesis to ancients", "err", err) log.Error("Error writing genesis to ancients", "err", err)
return 0, err return 0, err
@ -1385,7 +1403,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
// existing local chain segments (reorg around the chain tip). The reorganized part // existing local chain segments (reorg around the chain tip). The reorganized part
// will be included in the provided chain segment, and stale canonical markers will be // will be included in the provided chain segment, and stale canonical markers will be
// silently rewritten. Therefore, no explicit reorg logic is needed. // silently rewritten. Therefore, no explicit reorg logic is needed.
writeLive := func(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) { writeLive := func(blockChain types.Blocks, receiptChain []rlp.RawValue) (int, error) {
var ( var (
skipPresenceCheck = false skipPresenceCheck = false
batch = bc.db.NewBatch() batch = bc.db.NewBatch()
@ -1410,7 +1428,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
// Write all the data out into the database // Write all the data out into the database
rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64()) rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64())
rawdb.WriteBlock(batch, block) rawdb.WriteBlock(batch, block)
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receiptChain[i]) rawdb.WriteRawReceipts(batch, block.Hash(), block.NumberU64(), receiptChain[i])
// Write everything belongs to the blocks into the database. So that // Write everything belongs to the blocks into the database. So that
// we can ensure all components of body is completed(body, receipts) // we can ensure all components of body is completed(body, receipts)
@ -1758,18 +1776,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
bc.reportBlock(block, nil, err) bc.reportBlock(block, nil, err)
return nil, it.index, err return nil, it.index, err
} }
// No validation errors for the first block (or chain prefix skipped)
var activeState *state.StateDB
defer func() {
// The chain importer is starting and stopping trie prefetchers. If a bad
// block or other error is hit however, an early return may not properly
// terminate the background threads. This defer ensures that we clean up
// and dangling prefetcher, without deferring each and holding on live refs.
if activeState != nil {
activeState.StopPrefetcher()
}
}()
// Track the singleton witness from this chain insertion (if any) // Track the singleton witness from this chain insertion (if any)
var witness *stateless.Witness var witness *stateless.Witness
@ -1825,63 +1831,20 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
continue continue
} }
// Retrieve the parent block and it's state to execute on top // Retrieve the parent block and it's state to execute on top
start := time.Now()
parent := it.previous() parent := it.previous()
if parent == nil { if parent == nil {
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
} }
statedb, err := state.New(parent.Root, bc.statedb)
if err != nil {
return nil, it.index, err
}
// If we are past Byzantium, enable prefetching to pull in trie node paths
// while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction.
if bc.chainConfig.IsByzantium(block.Number()) {
// Generate witnesses either if we're self-testing, or if it's the
// only block being inserted. A bit crude, but witnesses are huge,
// so we refuse to make an entire chain of them.
if bc.vmConfig.StatelessSelfValidation || (makeWitness && len(chain) == 1) {
witness, err = stateless.NewWitness(block.Header(), bc)
if err != nil {
return nil, it.index, err
}
}
statedb.StartPrefetcher("chain", witness)
}
activeState = statedb
// If we have a followup block, run that against the current state to pre-cache
// transactions and probabilistically some of the account/storage trie nodes.
var followupInterrupt atomic.Bool
if !bc.cacheConfig.TrieCleanNoPrefetch {
if followup, err := it.peek(); followup != nil && err == nil {
throwaway, _ := state.New(parent.Root, bc.statedb)
go func(start time.Time, followup *types.Block, throwaway *state.StateDB) {
// Disable tracing for prefetcher executions.
vmCfg := bc.vmConfig
vmCfg.Tracer = nil
bc.prefetcher.Prefetch(followup, throwaway, vmCfg, &followupInterrupt)
blockPrefetchExecuteTimer.Update(time.Since(start))
if followupInterrupt.Load() {
blockPrefetchInterruptMeter.Mark(1)
}
}(time.Now(), followup, throwaway)
}
}
// The traced section of block import. // The traced section of block import.
res, err := bc.processBlock(block, statedb, start, setHead) start := time.Now()
followupInterrupt.Store(true) res, err := bc.processBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1)
if err != nil { if err != nil {
return nil, it.index, err return nil, it.index, err
} }
// Report the import stats before returning the various results // Report the import stats before returning the various results
stats.processed++ stats.processed++
stats.usedGas += res.usedGas stats.usedGas += res.usedGas
witness = res.witness
var snapDiffItems, snapBufItems common.StorageSize var snapDiffItems, snapBufItems common.StorageSize
if bc.snaps != nil { if bc.snaps != nil {
@ -1937,11 +1900,74 @@ type blockProcessingResult struct {
usedGas uint64 usedGas uint64
procTime time.Duration procTime time.Duration
status WriteStatus status WriteStatus
witness *stateless.Witness
} }
// 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(block *types.Block, statedb *state.StateDB, start time.Time, setHead bool) (_ *blockProcessingResult, blockEndErr error) { func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (_ *blockProcessingResult, blockEndErr error) {
var (
err error
startTime = time.Now()
statedb *state.StateDB
interrupt atomic.Bool
)
defer interrupt.Store(true) // terminate the prefetch at the end
if bc.cacheConfig.TrieCleanNoPrefetch {
statedb, err = state.New(parentRoot, bc.statedb)
if err != nil {
return nil, err
}
} else {
// If prefetching is enabled, run that against the current state to pre-cache
// transactions and probabilistically some of the account/storage trie nodes.
//
// Note: the main processor and prefetcher share the same reader with a local
// cache for mitigating the overhead of state access.
reader, err := bc.statedb.ReaderWithCache(parentRoot)
if err != nil {
return nil, err
}
throwaway, err := state.NewWithReader(parentRoot, bc.statedb, reader)
if err != nil {
return nil, err
}
statedb, err = state.NewWithReader(parentRoot, bc.statedb, reader)
if err != nil {
return nil, err
}
go func(start time.Time, throwaway *state.StateDB, block *types.Block) {
// Disable tracing for prefetcher executions.
vmCfg := bc.vmConfig
vmCfg.Tracer = nil
bc.prefetcher.Prefetch(block, throwaway, vmCfg, &interrupt)
blockPrefetchExecuteTimer.Update(time.Since(start))
if interrupt.Load() {
blockPrefetchInterruptMeter.Mark(1)
}
}(time.Now(), throwaway, block)
}
// If we are past Byzantium, enable prefetching to pull in trie node paths
// while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction.
var witness *stateless.Witness
if bc.chainConfig.IsByzantium(block.Number()) {
// Generate witnesses either if we're self-testing, or if it's the
// only block being inserted. A bit crude, but witnesses are huge,
// so we refuse to make an entire chain of them.
if bc.vmConfig.StatelessSelfValidation || makeWitness {
witness, err = stateless.NewWitness(block.Header(), bc)
if err != nil {
return nil, err
}
}
statedb.StartPrefetcher("chain", witness)
defer statedb.StopPrefetcher()
}
if bc.logger != nil && bc.logger.OnBlockStart != nil { if bc.logger != nil && bc.logger.OnBlockStart != nil {
bc.logger.OnBlockStart(tracing.BlockEvent{ bc.logger.OnBlockStart(tracing.BlockEvent{
Block: block, Block: block,
@ -2000,7 +2026,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
} }
} }
xvtime := time.Since(xvstart) xvtime := time.Since(xvstart)
proctime := time.Since(start) // processing + validation + cross validation proctime := time.Since(startTime) // processing + validation + cross validation
// Update the metrics touched during block processing and validation // Update the metrics touched during block processing and validation
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing) accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing)
@ -2041,9 +2067,14 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits) blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits)
blockInsertTimer.UpdateSince(start) blockInsertTimer.UpdateSince(startTime)
return &blockProcessingResult{usedGas: res.GasUsed, procTime: proctime, status: status}, nil return &blockProcessingResult{
usedGas: res.GasUsed,
procTime: proctime,
status: status,
witness: witness,
}, nil
} }
// insertSideChain is called when an import batch hits upon a pruned ancestor // insertSideChain is called when an import batch hits upon a pruned ancestor
@ -2527,14 +2558,22 @@ func (bc *BlockChain) reportBlock(block *types.Block, res *ProcessResult, err er
// logForkReadiness will write a log when a future fork is scheduled, but not // logForkReadiness will write a log when a future fork is scheduled, but not
// active. This is useful so operators know their client is ready for the fork. // active. This is useful so operators know their client is ready for the fork.
func (bc *BlockChain) logForkReadiness(block *types.Block) { func (bc *BlockChain) logForkReadiness(block *types.Block) {
c := bc.Config() config := bc.Config()
current, last := c.LatestFork(block.Time()), c.LatestFork(math.MaxUint64) current, last := config.LatestFork(block.Time()), config.LatestFork(math.MaxUint64)
t := c.Timestamp(last)
if t == nil { // Short circuit if the timestamp of the last fork is undefined,
// or if the network has already passed the last configured fork.
t := config.Timestamp(last)
if t == nil || current >= last {
return return
} }
at := time.Unix(int64(*t), 0) at := time.Unix(int64(*t), 0)
if current < last && time.Now().After(bc.lastForkReadyAlert.Add(forkReadyInterval)) {
// Only log if:
// - Current time is before the fork activation time
// - Enough time has passed since last alert
now := time.Now()
if now.Before(at) && now.After(bc.lastForkReadyAlert.Add(forkReadyInterval)) {
log.Info("Ready for fork activation", "fork", last, "date", at.Format(time.RFC822), log.Info("Ready for fork activation", "fork", last, "date", at.Format(time.RFC822),
"remaining", time.Until(at).Round(time.Second), "timestamp", at.Unix()) "remaining", time.Until(at).Round(time.Second), "timestamp", at.Unix())
bc.lastForkReadyAlert = time.Now() bc.lastForkReadyAlert = time.Now()
@ -2610,7 +2649,7 @@ func (bc *BlockChain) InsertHeadersBeforeCutoff(headers []*types.Header) (int, e
first = headers[0].Number.Uint64() first = headers[0].Number.Uint64()
) )
if first == 1 && frozen == 0 { if first == 1 && frozen == 0 {
_, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []types.Receipts{nil}) _, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []rlp.RawValue{rlp.EmptyList})
if err != nil { if err != nil {
log.Error("Error writing genesis to ancients", "err", err) log.Error("Error writing genesis to ancients", "err", err)
return 0, err return 0, err

View file

@ -27,10 +27,10 @@ import (
// insertStats tracks and reports on block insertion. // insertStats tracks and reports on block insertion.
type insertStats struct { type insertStats struct {
queued, processed, ignored int processed, ignored int
usedGas uint64 usedGas uint64
lastIndex int lastIndex int
startTime mclock.AbsTime startTime mclock.AbsTime
} }
// statsReportLimit is the time limit during import and export after which we // statsReportLimit is the time limit during import and export after which we
@ -43,8 +43,12 @@ func (st *insertStats) report(chain []*types.Block, index int, snapDiffItems, sn
// Fetch the timings for the batch // Fetch the timings for the batch
var ( var (
now = mclock.Now() now = mclock.Now()
elapsed = now.Sub(st.startTime) elapsed = now.Sub(st.startTime) + 1 // prevent zero division
mgasps = float64(st.usedGas) * 1000 / float64(elapsed)
) )
// Update the Mgas per second gauge
chainMgaspsGauge.Update(int64(mgasps))
// If we're at the last block of the batch or report period reached, log // If we're at the last block of the batch or report period reached, log
if index == len(chain)-1 || elapsed >= statsReportLimit { if index == len(chain)-1 || elapsed >= statsReportLimit {
// Count the number of transactions in this segment // Count the number of transactions in this segment
@ -58,7 +62,7 @@ func (st *insertStats) report(chain []*types.Block, index int, snapDiffItems, sn
context := []interface{}{ context := []interface{}{
"number", end.Number(), "hash", end.Hash(), "number", end.Number(), "hash", end.Hash(),
"blocks", st.processed, "txs", txs, "mgas", float64(st.usedGas) / 1000000, "blocks", st.processed, "txs", txs, "mgas", float64(st.usedGas) / 1000000,
"elapsed", common.PrettyDuration(elapsed), "mgasps", float64(st.usedGas) * 1000 / float64(elapsed), "elapsed", common.PrettyDuration(elapsed), "mgasps", mgasps,
} }
if timestamp := time.Unix(int64(end.Time()), 0); time.Since(timestamp) > time.Minute { if timestamp := time.Unix(int64(end.Time()), 0); time.Since(timestamp) > time.Minute {
context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...)
@ -74,9 +78,6 @@ func (st *insertStats) report(chain []*types.Block, index int, snapDiffItems, sn
} }
context = append(context, []interface{}{"triedirty", triebufNodes}...) context = append(context, []interface{}{"triedirty", triebufNodes}...)
if st.queued > 0 {
context = append(context, []interface{}{"queued", st.queued}...)
}
if st.ignored > 0 { if st.ignored > 0 {
context = append(context, []interface{}{"ignored", st.ignored}...) context = append(context, []interface{}{"ignored", st.ignored}...)
} }
@ -138,6 +139,7 @@ func (it *insertIterator) next() (*types.Block, error) {
// //
// Both header and body validation errors (nil too) is cached into the iterator // Both header and body validation errors (nil too) is cached into the iterator
// to avoid duplicating work on the following next() call. // to avoid duplicating work on the following next() call.
// nolint:unused
func (it *insertIterator) peek() (*types.Block, error) { func (it *insertIterator) peek() (*types.Block, error) {
// If we reached the end of the chain, abort // If we reached the end of the chain, abort
if it.index+1 >= len(it.chain) { if it.index+1 >= len(it.chain) {

View file

@ -234,12 +234,22 @@ func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
return receipts return receipts
} }
func (bc *BlockChain) GetRawReceiptsByHash(hash common.Hash) types.Receipts { // GetRawReceipts retrieves the receipts for all transactions in a given block
// without deriving the internal fields and the Bloom.
func (bc *BlockChain) GetRawReceipts(hash common.Hash, number uint64) types.Receipts {
if receipts, ok := bc.receiptsCache.Get(hash); ok {
return receipts
}
return rawdb.ReadRawReceipts(bc.db, hash, number)
}
// GetReceiptsRLP retrieves the receipts of a block.
func (bc *BlockChain) GetReceiptsRLP(hash common.Hash) rlp.RawValue {
number := rawdb.ReadHeaderNumber(bc.db, hash) number := rawdb.ReadHeaderNumber(bc.db, hash)
if number == nil { if number == nil {
return nil return nil
} }
return rawdb.ReadRawReceipts(bc.db, hash, *number) return rawdb.ReadReceiptsRLP(bc.db, hash, *number)
} }
// GetUnclesInChain retrieves all the uncles from a given block backwards until // GetUnclesInChain retrieves all the uncles from a given block backwards until

View file

@ -1769,7 +1769,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err) t.Fatalf("Failed to create persistent key-value database: %v", err)
} }
db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false) db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent freezer database: %v", err) t.Fatalf("Failed to create persistent freezer database: %v", err)
} }
@ -1791,7 +1791,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
} }
) )
defer engine.Close() defer engine.Close()
if snapshots { if snapshots && scheme == rawdb.HashScheme {
config.SnapshotLimit = 256 config.SnapshotLimit = 256
config.SnapshotWait = true config.SnapshotWait = true
} }
@ -1820,7 +1820,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
if err := chain.triedb.Commit(canonblocks[tt.commitBlock-1].Root(), false); err != nil { if err := chain.triedb.Commit(canonblocks[tt.commitBlock-1].Root(), false); err != nil {
t.Fatalf("Failed to flush trie state: %v", err) t.Fatalf("Failed to flush trie state: %v", err)
} }
if snapshots { if snapshots && scheme == rawdb.HashScheme {
if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil { if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil {
t.Fatalf("Failed to flatten snapshots: %v", err) t.Fatalf("Failed to flatten snapshots: %v", err)
} }
@ -1854,7 +1854,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
if err != nil { if err != nil {
t.Fatalf("Failed to reopen persistent key-value database: %v", err) t.Fatalf("Failed to reopen persistent key-value database: %v", err)
} }
db, err = rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false) db, err = rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
if err != nil { if err != nil {
t.Fatalf("Failed to reopen persistent freezer database: %v", err) t.Fatalf("Failed to reopen persistent freezer database: %v", err)
} }
@ -1919,7 +1919,7 @@ func testIssue23496(t *testing.T, scheme string) {
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err) t.Fatalf("Failed to create persistent key-value database: %v", err)
} }
db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false) db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent freezer database: %v", err) t.Fatalf("Failed to create persistent freezer database: %v", err)
} }
@ -1952,8 +1952,10 @@ func testIssue23496(t *testing.T, scheme string) {
if _, err := chain.InsertChain(blocks[1:2]); err != nil { if _, err := chain.InsertChain(blocks[1:2]); err != nil {
t.Fatalf("Failed to import canonical chain start: %v", err) t.Fatalf("Failed to import canonical chain start: %v", err)
} }
if err := chain.snaps.Cap(blocks[1].Root(), 0); err != nil { if scheme == rawdb.HashScheme {
t.Fatalf("Failed to flatten snapshots: %v", err) if err := chain.snaps.Cap(blocks[1].Root(), 0); err != nil {
t.Fatalf("Failed to flatten snapshots: %v", err)
}
} }
// Insert block B3 and commit the state into disk // Insert block B3 and commit the state into disk
@ -1977,7 +1979,7 @@ func testIssue23496(t *testing.T, scheme string) {
if err != nil { if err != nil {
t.Fatalf("Failed to reopen persistent key-value database: %v", err) t.Fatalf("Failed to reopen persistent key-value database: %v", err)
} }
db, err = rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false) db, err = rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
if err != nil { if err != nil {
t.Fatalf("Failed to reopen persistent freezer database: %v", err) t.Fatalf("Failed to reopen persistent freezer database: %v", err)
} }
@ -1997,15 +1999,23 @@ func testIssue23496(t *testing.T, scheme string) {
} }
expHead := uint64(1) expHead := uint64(1)
if scheme == rawdb.PathScheme { if scheme == rawdb.PathScheme {
expHead = uint64(2) // The pathdb database makes sure that snapshot and trie are consistent,
// so only the last block is reverted in case of a crash.
expHead = uint64(3)
} }
if head := chain.CurrentBlock(); head.Number.Uint64() != expHead { if head := chain.CurrentBlock(); head.Number.Uint64() != expHead {
t.Errorf("Head block mismatch: have %d, want %d", head.Number, expHead) t.Errorf("Head block mismatch: have %d, want %d", head.Number, expHead)
} }
if scheme == rawdb.PathScheme {
// Reinsert B2-B4 // Reinsert B4
if _, err := chain.InsertChain(blocks[1:]); err != nil { if _, err := chain.InsertChain(blocks[3:]); err != nil {
t.Fatalf("Failed to import canonical chain tail: %v", err) t.Fatalf("Failed to import canonical chain tail: %v", err)
}
} else {
// Reinsert B2-B4
if _, err := chain.InsertChain(blocks[1:]); err != nil {
t.Fatalf("Failed to import canonical chain tail: %v", err)
}
} }
if head := chain.CurrentHeader(); head.Number.Uint64() != uint64(4) { if head := chain.CurrentHeader(); head.Number.Uint64() != uint64(4) {
t.Errorf("Head header mismatch: have %d, want %d", head.Number, 4) t.Errorf("Head header mismatch: have %d, want %d", head.Number, 4)
@ -2016,7 +2026,9 @@ func testIssue23496(t *testing.T, scheme string) {
if head := chain.CurrentBlock(); head.Number.Uint64() != uint64(4) { if head := chain.CurrentBlock(); head.Number.Uint64() != uint64(4) {
t.Errorf("Head block mismatch: have %d, want %d", head.Number, uint64(4)) t.Errorf("Head block mismatch: have %d, want %d", head.Number, uint64(4))
} }
if layer := chain.Snapshots().Snapshot(blocks[2].Root()); layer == nil { if scheme == rawdb.HashScheme {
t.Error("Failed to regenerate the snapshot of known state") if layer := chain.Snapshots().Snapshot(blocks[2].Root()); layer == nil {
t.Error("Failed to regenerate the snapshot of known state")
}
} }
} }

View file

@ -1973,7 +1973,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err) t.Fatalf("Failed to create persistent key-value database: %v", err)
} }
db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false) db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent freezer database: %v", err) t.Fatalf("Failed to create persistent freezer database: %v", err)
} }
@ -2023,7 +2023,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
} }
if tt.commitBlock > 0 { if tt.commitBlock > 0 {
chain.triedb.Commit(canonblocks[tt.commitBlock-1].Root(), false) chain.triedb.Commit(canonblocks[tt.commitBlock-1].Root(), false)
if snapshots { if snapshots && scheme == rawdb.HashScheme {
if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil { if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil {
t.Fatalf("Failed to flatten snapshots: %v", err) t.Fatalf("Failed to flatten snapshots: %v", err)
} }

View file

@ -70,7 +70,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err) t.Fatalf("Failed to create persistent key-value database: %v", err)
} }
db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false) db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent freezer database: %v", err) t.Fatalf("Failed to create persistent freezer database: %v", err)
} }
@ -105,7 +105,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
if basic.commitBlock > 0 && basic.commitBlock == point { if basic.commitBlock > 0 && basic.commitBlock == point {
chain.TrieDB().Commit(blocks[point-1].Root(), false) chain.TrieDB().Commit(blocks[point-1].Root(), false)
} }
if basic.snapshotBlock > 0 && basic.snapshotBlock == point { if basic.snapshotBlock > 0 && basic.snapshotBlock == point && basic.scheme == rawdb.HashScheme {
// Flushing the entire snap tree into the disk, the // Flushing the entire snap tree into the disk, the
// relevant (a) snapshot root and (b) snapshot generator // relevant (a) snapshot root and (b) snapshot generator
// will be persisted atomically. // will be persisted atomically.
@ -149,13 +149,17 @@ func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks [
block := chain.GetBlockByNumber(basic.expSnapshotBottom) block := chain.GetBlockByNumber(basic.expSnapshotBottom)
if block == nil { if block == nil {
t.Errorf("The corresponding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom) t.Errorf("The corresponding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom)
} else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) { } else if basic.scheme == rawdb.HashScheme {
t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot()) if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) {
t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot())
}
} }
// Check the snapshot, ensure it's integrated // Check the snapshot, ensure it's integrated
if err := chain.snaps.Verify(block.Root()); err != nil { if basic.scheme == rawdb.HashScheme {
t.Errorf("The disk layer is not integrated %v", err) if err := chain.snaps.Verify(block.Root()); err != nil {
t.Errorf("The disk layer is not integrated %v", err)
}
} }
} }
@ -261,7 +265,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err) t.Fatalf("Failed to create persistent key-value database: %v", err)
} }
newdb, err := rawdb.NewDatabaseWithFreezer(pdb, snaptest.ancient, "", false) newdb, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: snaptest.ancient})
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent freezer database: %v", err) t.Fatalf("Failed to create persistent freezer database: %v", err)
} }
@ -565,12 +569,14 @@ func TestHighCommitCrashWithNewSnapshot(t *testing.T) {
// //
// Expected head header : C8 // Expected head header : C8
// Expected head fast block: C8 // Expected head fast block: C8
// Expected head block : G // Expected head block : G (Hash mode), C6 (Hash mode)
// Expected snapshot disk : C4 // Expected snapshot disk : C4 (Hash mode)
for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} { for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} {
expHead := uint64(0) expHead := uint64(0)
if scheme == rawdb.PathScheme { if scheme == rawdb.PathScheme {
expHead = uint64(4) // The pathdb database makes sure that snapshot and trie are consistent,
// so only the last two blocks are reverted in case of a crash.
expHead = uint64(6)
} }
test := &crashSnapshotTest{ test := &crashSnapshotTest{
snapshotTestBasic{ snapshotTestBasic{

View file

@ -734,11 +734,11 @@ func testFastVsFullChains(t *testing.T, scheme string) {
fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil) fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer fast.Stop() defer fast.Stop()
if n, err := fast.InsertReceiptChain(blocks, receipts, 0); err != nil { if n, err := fast.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
// Freezer style fast import the chain. // Freezer style fast import the chain.
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) ancientDb, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
if err != nil { if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err) t.Fatalf("failed to create temp freezer db: %v", err)
} }
@ -747,7 +747,7 @@ func testFastVsFullChains(t *testing.T, scheme string) {
ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil) ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer ancient.Stop() defer ancient.Stop()
if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(len(blocks)/2)); err != nil { if n, err := ancient.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), uint64(len(blocks)/2)); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
@ -824,7 +824,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
// makeDb creates a db instance for testing. // makeDb creates a db instance for testing.
makeDb := func() ethdb.Database { makeDb := func() ethdb.Database {
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) db, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
if err != nil { if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err) t.Fatalf("failed to create temp freezer db: %v", err)
} }
@ -871,7 +871,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil) fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer fast.Stop() defer fast.Stop()
if n, err := fast.InsertReceiptChain(blocks, receipts, 0); err != nil { if n, err := fast.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
assert(t, "fast", fast, height, height, 0) assert(t, "fast", fast, height, height, 0)
@ -884,7 +884,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil) ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer ancient.Stop() defer ancient.Stop()
if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(3*len(blocks)/4)); err != nil { if n, err := ancient.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), uint64(3*len(blocks)/4)); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
assert(t, "ancient", ancient, height, height, 0) assert(t, "ancient", ancient, height, height, 0)
@ -1623,7 +1623,7 @@ func testLargeReorgTrieGC(t *testing.T, scheme string) {
competitor, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*state.TriesInMemory+1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{3}) }) competitor, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*state.TriesInMemory+1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{3}) })
// Import the shared chain and the original canonical one // Import the shared chain and the original canonical one
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
defer db.Close() defer db.Close()
chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil) chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil)
@ -1689,14 +1689,14 @@ func testBlockchainRecovery(t *testing.T, scheme string) {
_, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil) _, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil)
// Import the chain as a ancient-first node and ensure all pointers are updated // Import the chain as a ancient-first node and ensure all pointers are updated
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) ancientDb, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{Ancient: t.TempDir()})
if err != nil { if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err) t.Fatalf("failed to create temp freezer db: %v", err)
} }
defer ancientDb.Close() defer ancientDb.Close()
ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil) ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(3*len(blocks)/4)); err != nil { if n, err := ancient.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), uint64(3*len(blocks)/4)); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
rawdb.WriteLastPivotNumber(ancientDb, blocks[len(blocks)-1].NumberU64()) // Force fast sync behavior rawdb.WriteLastPivotNumber(ancientDb, blocks[len(blocks)-1].NumberU64()) // Force fast sync behavior
@ -1747,7 +1747,7 @@ func testLowDiffLongChain(t *testing.T, scheme string) {
}) })
// Import the canonical chain // Import the canonical chain
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) diskdb, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
defer diskdb.Close() defer diskdb.Close()
chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil) chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil)
@ -1959,7 +1959,7 @@ func testInsertKnownChainData(t *testing.T, typ string, scheme string) {
b.OffsetTime(-9) // A higher difficulty b.OffsetTime(-9) // A higher difficulty
}) })
// Import the shared chain and the original canonical one // Import the shared chain and the original canonical one
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) chaindb, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
if err != nil { if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err) t.Fatalf("failed to create temp freezer db: %v", err)
} }
@ -1991,7 +1991,7 @@ func testInsertKnownChainData(t *testing.T, typ string, scheme string) {
} }
} else if typ == "receipts" { } else if typ == "receipts" {
inserter = func(blocks []*types.Block, receipts []types.Receipts) error { inserter = func(blocks []*types.Block, receipts []types.Receipts) error {
_, err = chain.InsertReceiptChain(blocks, receipts, 0) _, err = chain.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0)
return err return err
} }
asserter = func(t *testing.T, block *types.Block) { asserter = func(t *testing.T, block *types.Block) {
@ -2122,7 +2122,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i
} }
}) })
// Import the shared chain and the original canonical one // Import the shared chain and the original canonical one
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) chaindb, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
if err != nil { if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err) t.Fatalf("failed to create temp freezer db: %v", err)
} }
@ -2157,7 +2157,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i
} }
} else if typ == "receipts" { } else if typ == "receipts" {
inserter = func(blocks []*types.Block, receipts []types.Receipts) error { inserter = func(blocks []*types.Block, receipts []types.Receipts) error {
_, err = chain.InsertReceiptChain(blocks, receipts, 0) _, err = chain.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0)
return err return err
} }
asserter = func(t *testing.T, block *types.Block) { asserter = func(t *testing.T, block *types.Block) {
@ -2496,7 +2496,7 @@ func testSideImportPrunedBlocks(t *testing.T, scheme string) {
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err) t.Fatalf("Failed to create persistent key-value database: %v", err)
} }
db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false) db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
if err != nil { if err != nil {
t.Fatalf("Failed to create persistent freezer database: %v", err) t.Fatalf("Failed to create persistent freezer database: %v", err)
} }
@ -3403,7 +3403,7 @@ func testSetCanonical(t *testing.T, scheme string) {
} }
gen.AddTx(tx) gen.AddTx(tx)
}) })
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) diskdb, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
defer diskdb.Close() defer diskdb.Close()
chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil) chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil)
@ -4199,16 +4199,16 @@ func testChainReorgSnapSync(t *testing.T, ancientLimit uint64) {
gen.SetCoinbase(common.Address{0: byte(0xb), 19: byte(i)}) gen.SetCoinbase(common.Address{0: byte(0xb), 19: byte(i)})
}) })
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
defer db.Close() defer db.Close()
chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil) chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
defer chain.Stop() defer chain.Stop()
if n, err := chain.InsertReceiptChain(blocks, receipts, ancientLimit); err != nil { if n, err := chain.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), ancientLimit); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
if n, err := chain.InsertReceiptChain(chainA, receiptsA, ancientLimit); err != nil { if n, err := chain.InsertReceiptChain(chainA, types.EncodeBlockReceiptLists(receiptsA), ancientLimit); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
// If the common ancestor is below the ancient limit, rewind the chain head. // If the common ancestor is below the ancient limit, rewind the chain head.
@ -4218,7 +4218,7 @@ func testChainReorgSnapSync(t *testing.T, ancientLimit uint64) {
rawdb.WriteLastPivotNumber(db, ancestor) rawdb.WriteLastPivotNumber(db, ancestor)
chain.SetHead(ancestor) chain.SetHead(ancestor)
} }
if n, err := chain.InsertReceiptChain(chainB, receiptsB, ancientLimit); err != nil { if n, err := chain.InsertReceiptChain(chainB, types.EncodeBlockReceiptLists(receiptsB), ancientLimit); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
head := chain.CurrentSnapBlock() head := chain.CurrentSnapBlock()
@ -4315,7 +4315,7 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64,
config := DefaultCacheConfigWithScheme(rawdb.PathScheme) config := DefaultCacheConfigWithScheme(rawdb.PathScheme)
config.ChainHistoryMode = history.KeepPostMerge config.ChainHistoryMode = history.KeepPostMerge
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
defer db.Close() defer db.Close()
chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), genesis, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil) chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), genesis, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
defer chain.Stop() defer chain.Stop()
@ -4336,7 +4336,7 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64,
if n, err := chain.InsertHeadersBeforeCutoff(headersBefore); err != nil { if n, err := chain.InsertHeadersBeforeCutoff(headersBefore); err != nil {
t.Fatalf("failed to insert headers before cutoff %d: %v", n, err) t.Fatalf("failed to insert headers before cutoff %d: %v", n, err)
} }
if n, err := chain.InsertReceiptChain(blocksAfter, receiptsAfter, ancientLimit); err != nil { if n, err := chain.InsertReceiptChain(blocksAfter, types.EncodeBlockReceiptLists(receiptsAfter), ancientLimit); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
headSnap := chain.CurrentSnapBlock() headSnap := chain.CurrentSnapBlock()

View file

@ -29,7 +29,7 @@ type blockchain interface {
GetHeader(hash common.Hash, number uint64) *types.Header GetHeader(hash common.Hash, number uint64) *types.Header
GetCanonicalHash(number uint64) common.Hash GetCanonicalHash(number uint64) common.Hash
GetReceiptsByHash(hash common.Hash) types.Receipts GetReceiptsByHash(hash common.Hash) types.Receipts
GetRawReceiptsByHash(hash common.Hash) types.Receipts GetRawReceipts(hash common.Hash, number uint64) types.Receipts
} }
// ChainView represents an immutable view of a chain with a block id and a set // ChainView represents an immutable view of a chain with a block id and a set
@ -117,7 +117,7 @@ func (cv *ChainView) RawReceipts(number uint64) types.Receipts {
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber) log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber)
return nil return nil
} }
return cv.chain.GetRawReceiptsByHash(blockHash) return cv.chain.GetRawReceipts(blockHash, number)
} }
// SharedRange returns the block range shared by two chain views. // SharedRange returns the block range shared by two chain views.

View file

@ -53,7 +53,6 @@ const (
databaseVersion = 2 // reindexed if database version does not match databaseVersion = 2 // reindexed if database version does not match
cachedLastBlocks = 1000 // last block of map pointers cachedLastBlocks = 1000 // last block of map pointers
cachedLvPointers = 1000 // first log value pointer of block pointers cachedLvPointers = 1000 // first log value pointer of block pointers
cachedBaseRows = 100 // groups of base layer filter row data
cachedFilterMaps = 3 // complete filter maps (cached by map renderer) cachedFilterMaps = 3 // complete filter maps (cached by map renderer)
cachedRenderSnapshots = 8 // saved map renderer data at block boundaries cachedRenderSnapshots = 8 // saved map renderer data at block boundaries
) )
@ -101,7 +100,6 @@ type FilterMaps struct {
filterMapCache *lru.Cache[uint32, filterMap] filterMapCache *lru.Cache[uint32, filterMap]
lastBlockCache *lru.Cache[uint32, lastBlockOfMap] lastBlockCache *lru.Cache[uint32, lastBlockOfMap]
lvPointerCache *lru.Cache[uint64, uint64] lvPointerCache *lru.Cache[uint64, uint64]
baseRowsCache *lru.Cache[uint64, [][]uint32]
// the matchers set and the fields of FilterMapsMatcherBackend instances are // the matchers set and the fields of FilterMapsMatcherBackend instances are
// read and written both by exported functions and the indexer. // read and written both by exported functions and the indexer.
@ -227,7 +225,7 @@ type Config struct {
// NewFilterMaps creates a new FilterMaps and starts the indexer. // NewFilterMaps creates a new FilterMaps and starts the indexer.
func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, finalBlock uint64, params Params, config Config) *FilterMaps { func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, finalBlock uint64, params Params, config Config) *FilterMaps {
rs, initialized, err := rawdb.ReadFilterMapsRange(db) rs, initialized, err := rawdb.ReadFilterMapsRange(db)
if err != nil || rs.Version != databaseVersion { if err != nil || (initialized && rs.Version != databaseVersion) {
rs, initialized = rawdb.FilterMapsRange{}, false rs, initialized = rawdb.FilterMapsRange{}, false
log.Warn("Invalid log index database version; resetting log index") log.Warn("Invalid log index database version; resetting log index")
} }
@ -264,7 +262,6 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
filterMapCache: lru.NewCache[uint32, filterMap](cachedFilterMaps), filterMapCache: lru.NewCache[uint32, filterMap](cachedFilterMaps),
lastBlockCache: lru.NewCache[uint32, lastBlockOfMap](cachedLastBlocks), lastBlockCache: lru.NewCache[uint32, lastBlockOfMap](cachedLastBlocks),
lvPointerCache: lru.NewCache[uint64, uint64](cachedLvPointers), lvPointerCache: lru.NewCache[uint64, uint64](cachedLvPointers),
baseRowsCache: lru.NewCache[uint64, [][]uint32](cachedBaseRows),
renderSnapshots: lru.NewCache[uint64, *renderedMap](cachedRenderSnapshots), renderSnapshots: lru.NewCache[uint64, *renderedMap](cachedRenderSnapshots),
} }
f.checkRevertRange() // revert maps that are inconsistent with the current chain view f.checkRevertRange() // revert maps that are inconsistent with the current chain view
@ -348,7 +345,6 @@ func (f *FilterMaps) reset() {
f.renderSnapshots.Purge() f.renderSnapshots.Purge()
f.lastBlockCache.Purge() f.lastBlockCache.Purge()
f.lvPointerCache.Purge() f.lvPointerCache.Purge()
f.baseRowsCache.Purge()
f.indexLock.Unlock() f.indexLock.Unlock()
// deleting the range first ensures that resetDb will be called again at next // deleting the range first ensures that resetDb will be called again at next
// startup and any leftover data will be removed even if it cannot finish now. // startup and any leftover data will be removed even if it cannot finish now.
@ -565,47 +561,69 @@ func (f *FilterMaps) getFilterMap(mapIndex uint32) (filterMap, error) {
} }
fm := make(filterMap, f.mapHeight) fm := make(filterMap, f.mapHeight)
for rowIndex := range fm { for rowIndex := range fm {
var err error rows, err := f.getFilterMapRows([]uint32{mapIndex}, uint32(rowIndex), false)
fm[rowIndex], err = f.getFilterMapRow(mapIndex, uint32(rowIndex), false)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load filter map %d from database: %v", mapIndex, err) return nil, fmt.Errorf("failed to load filter map %d from database: %v", mapIndex, err)
} }
fm[rowIndex] = rows[0]
} }
f.filterMapCache.Add(mapIndex, fm) f.filterMapCache.Add(mapIndex, fm)
return fm, nil return fm, nil
} }
// getFilterMapRow fetches the given filter map row. If baseLayerOnly is true // getFilterMapRows fetches a set of filter map rows at the corresponding map
// then only the first baseRowLength entries are returned. // indices and a shared row index. If baseLayerOnly is true then only the first
func (f *FilterMaps) getFilterMapRow(mapIndex, rowIndex uint32, baseLayerOnly bool) (FilterRow, error) { // baseRowLength entries are returned.
baseMapRowIndex := f.mapRowIndex(mapIndex&-f.baseRowGroupLength, rowIndex) func (f *FilterMaps) getFilterMapRows(mapIndices []uint32, rowIndex uint32, baseLayerOnly bool) ([]FilterRow, error) {
baseRows, ok := f.baseRowsCache.Get(baseMapRowIndex) rows := make([]FilterRow, len(mapIndices))
if !ok { var ptr int
var err error for len(mapIndices) > ptr {
baseRows, err = rawdb.ReadFilterMapBaseRows(f.db, baseMapRowIndex, f.baseRowGroupLength, f.logMapWidth) baseRowGroup := mapIndices[ptr] / f.baseRowGroupLength
if err != nil { groupLength := 1
return nil, fmt.Errorf("failed to retrieve filter map %d base rows %d: %v", mapIndex, rowIndex, err) for ptr+groupLength < len(mapIndices) && mapIndices[ptr+groupLength]/f.baseRowGroupLength == baseRowGroup {
groupLength++
} }
f.baseRowsCache.Add(baseMapRowIndex, baseRows) if err := f.getFilterMapRowsOfGroup(rows[ptr:ptr+groupLength], mapIndices[ptr:ptr+groupLength], rowIndex, baseLayerOnly); err != nil {
return nil, err
}
ptr += groupLength
} }
baseRow := slices.Clone(baseRows[mapIndex&(f.baseRowGroupLength-1)]) return rows, nil
if baseLayerOnly { }
return baseRow, nil
} // getFilterMapRowsOfGroup fetches a set of filter map rows at map indices
extRow, err := rawdb.ReadFilterMapExtRow(f.db, f.mapRowIndex(mapIndex, rowIndex), f.logMapWidth) // belonging to the same base row group.
func (f *FilterMaps) getFilterMapRowsOfGroup(target []FilterRow, mapIndices []uint32, rowIndex uint32, baseLayerOnly bool) error {
baseRowGroup := mapIndices[0] / f.baseRowGroupLength
baseMapRowIndex := f.mapRowIndex(baseRowGroup*f.baseRowGroupLength, rowIndex)
baseRows, err := rawdb.ReadFilterMapBaseRows(f.db, baseMapRowIndex, f.baseRowGroupLength, f.logMapWidth)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to retrieve filter map %d extended row %d: %v", mapIndex, rowIndex, err) return fmt.Errorf("failed to retrieve base row group %d of row %d: %v", baseRowGroup, rowIndex, err)
} }
return FilterRow(append(baseRow, extRow...)), nil for i, mapIndex := range mapIndices {
if mapIndex/f.baseRowGroupLength != baseRowGroup {
panic("mapIndices are not in the same base row group")
}
row := baseRows[mapIndex&(f.baseRowGroupLength-1)]
if !baseLayerOnly {
extRow, err := rawdb.ReadFilterMapExtRow(f.db, f.mapRowIndex(mapIndex, rowIndex), f.logMapWidth)
if err != nil {
return fmt.Errorf("failed to retrieve filter map %d extended row %d: %v", mapIndex, rowIndex, err)
}
row = append(row, extRow...)
}
target[i] = row
}
return nil
} }
// storeFilterMapRows stores a set of filter map rows at the corresponding map // storeFilterMapRows stores a set of filter map rows at the corresponding map
// indices and a shared row index. // indices and a shared row index.
func (f *FilterMaps) storeFilterMapRows(batch ethdb.Batch, mapIndices []uint32, rowIndex uint32, rows []FilterRow) error { func (f *FilterMaps) storeFilterMapRows(batch ethdb.Batch, mapIndices []uint32, rowIndex uint32, rows []FilterRow) error {
for len(mapIndices) > 0 { for len(mapIndices) > 0 {
baseMapIndex := mapIndices[0] & -f.baseRowGroupLength baseRowGroup := mapIndices[0] / f.baseRowGroupLength
groupLength := 1 groupLength := 1
for groupLength < len(mapIndices) && mapIndices[groupLength]&-f.baseRowGroupLength == baseMapIndex { for groupLength < len(mapIndices) && mapIndices[groupLength]/f.baseRowGroupLength == baseRowGroup {
groupLength++ groupLength++
} }
if err := f.storeFilterMapRowsOfGroup(batch, mapIndices[:groupLength], rowIndex, rows[:groupLength]); err != nil { if err := f.storeFilterMapRowsOfGroup(batch, mapIndices[:groupLength], rowIndex, rows[:groupLength]); err != nil {
@ -619,26 +637,20 @@ func (f *FilterMaps) storeFilterMapRows(batch ethdb.Batch, mapIndices []uint32,
// storeFilterMapRowsOfGroup stores a set of filter map rows at map indices // storeFilterMapRowsOfGroup stores a set of filter map rows at map indices
// belonging to the same base row group. // belonging to the same base row group.
func (f *FilterMaps) storeFilterMapRowsOfGroup(batch ethdb.Batch, mapIndices []uint32, rowIndex uint32, rows []FilterRow) error { func (f *FilterMaps) storeFilterMapRowsOfGroup(batch ethdb.Batch, mapIndices []uint32, rowIndex uint32, rows []FilterRow) error {
baseMapIndex := mapIndices[0] & -f.baseRowGroupLength baseRowGroup := mapIndices[0] / f.baseRowGroupLength
baseMapRowIndex := f.mapRowIndex(baseMapIndex, rowIndex) baseMapRowIndex := f.mapRowIndex(baseRowGroup*f.baseRowGroupLength, rowIndex)
var baseRows [][]uint32 var baseRows [][]uint32
if uint32(len(mapIndices)) != f.baseRowGroupLength { // skip base rows read if all rows are replaced if uint32(len(mapIndices)) != f.baseRowGroupLength { // skip base rows read if all rows are replaced
var ok bool var err error
baseRows, ok = f.baseRowsCache.Get(baseMapRowIndex) baseRows, err = rawdb.ReadFilterMapBaseRows(f.db, baseMapRowIndex, f.baseRowGroupLength, f.logMapWidth)
if ok { if err != nil {
baseRows = slices.Clone(baseRows) return fmt.Errorf("failed to retrieve base row group %d of row %d for modification: %v", baseRowGroup, rowIndex, err)
} else {
var err error
baseRows, err = rawdb.ReadFilterMapBaseRows(f.db, baseMapRowIndex, f.baseRowGroupLength, f.logMapWidth)
if err != nil {
return fmt.Errorf("failed to retrieve filter map %d base rows %d for modification: %v", mapIndices[0]&-f.baseRowGroupLength, rowIndex, err)
}
} }
} else { } else {
baseRows = make([][]uint32, f.baseRowGroupLength) baseRows = make([][]uint32, f.baseRowGroupLength)
} }
for i, mapIndex := range mapIndices { for i, mapIndex := range mapIndices {
if mapIndex&-f.baseRowGroupLength != baseMapIndex { if mapIndex/f.baseRowGroupLength != baseRowGroup {
panic("mapIndices are not in the same base row group") panic("mapIndices are not in the same base row group")
} }
baseRow := []uint32(rows[i]) baseRow := []uint32(rows[i])
@ -650,7 +662,6 @@ func (f *FilterMaps) storeFilterMapRowsOfGroup(batch ethdb.Batch, mapIndices []u
baseRows[mapIndex&(f.baseRowGroupLength-1)] = baseRow baseRows[mapIndex&(f.baseRowGroupLength-1)] = baseRow
rawdb.WriteFilterMapExtRow(batch, f.mapRowIndex(mapIndex, rowIndex), extRow, f.logMapWidth) rawdb.WriteFilterMapExtRow(batch, f.mapRowIndex(mapIndex, rowIndex), extRow, f.logMapWidth)
} }
f.baseRowsCache.Add(baseMapRowIndex, baseRows)
rawdb.WriteFilterMapBaseRows(batch, baseMapRowIndex, baseRows, f.logMapWidth) rawdb.WriteFilterMapBaseRows(batch, baseMapRowIndex, baseRows, f.logMapWidth)
return nil return nil
} }

View file

@ -428,10 +428,12 @@ func (ts *testSetup) matcherViewHash() common.Hash {
binary.LittleEndian.PutUint32(enc[:4], r) binary.LittleEndian.PutUint32(enc[:4], r)
for m := uint32(0); m <= headMap; m++ { for m := uint32(0); m <= headMap; m++ {
binary.LittleEndian.PutUint32(enc[4:8], m) binary.LittleEndian.PutUint32(enc[4:8], m)
row, _ := mb.GetFilterMapRow(ctx, m, r, false) rows, _ := mb.GetFilterMapRows(ctx, []uint32{m}, r, false)
for _, v := range row { for _, row := range rows {
binary.LittleEndian.PutUint32(enc[8:], v) for _, v := range row {
hasher.Write(enc[:]) binary.LittleEndian.PutUint32(enc[8:], v)
hasher.Write(enc[:])
}
} }
} }
} }
@ -515,7 +517,7 @@ func (tc *testChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
return tc.receipts[hash] return tc.receipts[hash]
} }
func (tc *testChain) GetRawReceiptsByHash(hash common.Hash) types.Receipts { func (tc *testChain) GetRawReceipts(hash common.Hash, number uint64) types.Receipts {
tc.lock.RLock() tc.lock.RLock()
defer tc.lock.RUnlock() defer tc.lock.RUnlock()

View file

@ -20,7 +20,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"math"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -45,7 +44,7 @@ var ErrMatchAll = errors.New("match all patterns not supported")
type MatcherBackend interface { type MatcherBackend interface {
GetParams() *Params GetParams() *Params
GetBlockLvPointer(ctx context.Context, blockNumber uint64) (uint64, error) GetBlockLvPointer(ctx context.Context, blockNumber uint64) (uint64, error)
GetFilterMapRow(ctx context.Context, mapIndex, rowIndex uint32, baseLayerOnly bool) (FilterRow, error) GetFilterMapRows(ctx context.Context, mapIndices []uint32, rowIndex uint32, baseLayerOnly bool) ([]FilterRow, error)
GetLogByLvIndex(ctx context.Context, lvIndex uint64) (*types.Log, error) GetLogByLvIndex(ctx context.Context, lvIndex uint64) (*types.Log, error)
SyncLogIndex(ctx context.Context) (SyncRange, error) SyncLogIndex(ctx context.Context) (SyncRange, error)
Close() Close()
@ -365,50 +364,57 @@ func (m *singleMatcherInstance) getMatchesForLayer(ctx context.Context, layerInd
var st int var st int
m.stats.setState(&st, stOther) m.stats.setState(&st, stOther)
params := m.backend.GetParams() params := m.backend.GetParams()
maskedMapIndex, rowIndex := uint32(math.MaxUint32), uint32(0) var ptr int
for _, mapIndex := range m.mapIndices { for len(m.mapIndices) > ptr {
filterRows, ok := m.filterRows[mapIndex] // find next group of map indices mapped onto the same row
if !ok { maskedMapIndex := params.maskedMapIndex(m.mapIndices[ptr], layerIndex)
continue rowIndex := params.rowIndex(m.mapIndices[ptr], layerIndex, m.value)
} groupLength := 1
if mm := params.maskedMapIndex(mapIndex, layerIndex); mm != maskedMapIndex { for ptr+groupLength < len(m.mapIndices) && params.maskedMapIndex(m.mapIndices[ptr+groupLength], layerIndex) == maskedMapIndex {
// only recalculate rowIndex when necessary groupLength++
maskedMapIndex = mm
rowIndex = params.rowIndex(mapIndex, layerIndex, m.value)
} }
if layerIndex == 0 { if layerIndex == 0 {
m.stats.setState(&st, stFetchFirst) m.stats.setState(&st, stFetchFirst)
} else { } else {
m.stats.setState(&st, stFetchMore) m.stats.setState(&st, stFetchMore)
} }
filterRow, err := m.backend.GetFilterMapRow(ctx, mapIndex, rowIndex, layerIndex == 0) groupRows, err := m.backend.GetFilterMapRows(ctx, m.mapIndices[ptr:ptr+groupLength], rowIndex, layerIndex == 0)
if err != nil { if err != nil {
m.stats.setState(&st, stNone) m.stats.setState(&st, stNone)
return nil, fmt.Errorf("failed to retrieve filter map %d row %d: %v", mapIndex, rowIndex, err) return nil, fmt.Errorf("failed to retrieve filter map %d row %d: %v", m.mapIndices[ptr], rowIndex, err)
} }
if layerIndex == 0 {
matchBaseRowAccessMeter.Mark(1)
matchBaseRowSizeMeter.Mark(int64(len(filterRow)))
} else {
matchExtRowAccessMeter.Mark(1)
matchExtRowSizeMeter.Mark(int64(len(filterRow)))
}
m.stats.addAmount(st, int64(len(filterRow)))
m.stats.setState(&st, stOther) m.stats.setState(&st, stOther)
filterRows = append(filterRows, filterRow) for i := range groupLength {
if uint32(len(filterRow)) < params.maxRowLength(layerIndex) { mapIndex := m.mapIndices[ptr+i]
m.stats.setState(&st, stProcess) filterRow := groupRows[i]
matches := params.potentialMatches(filterRows, mapIndex, m.value) filterRows, ok := m.filterRows[mapIndex]
m.stats.addAmount(st, int64(len(matches))) if !ok {
results = append(results, matcherResult{ panic("dropped map in mapIndices")
mapIndex: mapIndex, }
matches: matches, if layerIndex == 0 {
}) matchBaseRowAccessMeter.Mark(1)
m.stats.setState(&st, stOther) matchBaseRowSizeMeter.Mark(int64(len(filterRow)))
delete(m.filterRows, mapIndex) } else {
} else { matchExtRowAccessMeter.Mark(1)
m.filterRows[mapIndex] = filterRows matchExtRowSizeMeter.Mark(int64(len(filterRow)))
}
m.stats.addAmount(st, int64(len(filterRow)))
filterRows = append(filterRows, filterRow)
if uint32(len(filterRow)) < params.maxRowLength(layerIndex) {
m.stats.setState(&st, stProcess)
matches := params.potentialMatches(filterRows, mapIndex, m.value)
m.stats.addAmount(st, int64(len(matches)))
results = append(results, matcherResult{
mapIndex: mapIndex,
matches: matches,
})
m.stats.setState(&st, stOther)
delete(m.filterRows, mapIndex)
} else {
m.filterRows[mapIndex] = filterRows
}
} }
ptr += groupLength
} }
m.cleanMapIndices() m.cleanMapIndices()
m.stats.setState(&st, stNone) m.stats.setState(&st, stNone)

View file

@ -67,18 +67,15 @@ func (fm *FilterMapsMatcherBackend) Close() {
delete(fm.f.matchers, fm) delete(fm.f.matchers, fm)
} }
// GetFilterMapRow returns the given row of the given map. If the row is empty // GetFilterMapRows returns the given row of the given map. If the row is empty
// then a non-nil zero length row is returned. If baseLayerOnly is true then // then a non-nil zero length row is returned. If baseLayerOnly is true then
// only the first baseRowLength entries of the row are guaranteed to be // only the first baseRowLength entries of the row are guaranteed to be
// returned. // returned.
// Note that the returned slices should not be modified, they should be copied // Note that the returned slices should not be modified, they should be copied
// on write. // on write.
// GetFilterMapRow implements MatcherBackend. // GetFilterMapRows implements MatcherBackend.
func (fm *FilterMapsMatcherBackend) GetFilterMapRow(ctx context.Context, mapIndex, rowIndex uint32, baseLayerOnly bool) (FilterRow, error) { func (fm *FilterMapsMatcherBackend) GetFilterMapRows(ctx context.Context, mapIndices []uint32, rowIndex uint32, baseLayerOnly bool) ([]FilterRow, error) {
fm.f.indexLock.RLock() return fm.f.getFilterMapRows(mapIndices, rowIndex, baseLayerOnly)
defer fm.f.indexLock.RUnlock()
return fm.f.getFilterMapRow(mapIndex, rowIndex, baseLayerOnly)
} }
// GetBlockLvPointer returns the starting log value index where the log values // GetBlockLvPointer returns the starting log value index where the log values

View file

@ -545,8 +545,8 @@ func ReadReceiptsRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawVa
} }
// ReadRawReceipts retrieves all the transaction receipts belonging to a block. // ReadRawReceipts retrieves all the transaction receipts belonging to a block.
// The receipt metadata fields are not guaranteed to be populated, so they // The receipt metadata fields and the Bloom are not guaranteed to be populated,
// should not be used. Use ReadReceipts instead if the metadata is needed. // so they should not be used. Use ReadReceipts instead if the metadata is needed.
func ReadRawReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Receipts { func ReadRawReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Receipts {
// Retrieve the flattened receipt slice // Retrieve the flattened receipt slice
data := ReadReceiptsRLP(db, hash, number) data := ReadReceiptsRLP(db, hash, number)
@ -621,6 +621,14 @@ func WriteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64, rec
} }
} }
// WriteRawReceipts stores all the transaction receipts belonging to a block.
func WriteRawReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64, receipts rlp.RawValue) {
// Store the flattened receipt slice
if err := db.Put(blockReceiptsKey(number, hash), receipts); err != nil {
log.Crit("Failed to store block receipts", "err", err)
}
}
// DeleteReceipts removes all receipt data associated with a block hash. // DeleteReceipts removes all receipt data associated with a block hash.
func DeleteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { func DeleteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
if err := db.Delete(blockReceiptsKey(number, hash)); err != nil { if err := db.Delete(blockReceiptsKey(number, hash)); err != nil {
@ -701,18 +709,11 @@ func WriteBlock(db ethdb.KeyValueWriter, block *types.Block) {
} }
// WriteAncientBlocks writes entire block data into ancient store and returns the total written size. // WriteAncientBlocks writes entire block data into ancient store and returns the total written size.
func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts []types.Receipts) (int64, error) { func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts []rlp.RawValue) (int64, error) {
var stReceipts []*types.ReceiptForStorage
return db.ModifyAncients(func(op ethdb.AncientWriteOp) error { return db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i, block := range blocks { for i, block := range blocks {
// Convert receipts to storage format and sum up total difficulty.
stReceipts = stReceipts[:0]
for _, receipt := range receipts[i] {
stReceipts = append(stReceipts, (*types.ReceiptForStorage)(receipt))
}
header := block.Header() header := block.Header()
if err := writeAncientBlock(op, block, header, stReceipts); err != nil { if err := writeAncientBlock(op, block, header, receipts[i]); err != nil {
return err return err
} }
} }
@ -720,7 +721,7 @@ func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts
}) })
} }
func writeAncientBlock(op ethdb.AncientWriteOp, block *types.Block, header *types.Header, receipts []*types.ReceiptForStorage) error { func writeAncientBlock(op ethdb.AncientWriteOp, block *types.Block, header *types.Header, receipts rlp.RawValue) error {
num := block.NumberU64() num := block.NumberU64()
if err := op.AppendRaw(ChainFreezerHashTable, num, block.Hash().Bytes()); err != nil { if err := op.AppendRaw(ChainFreezerHashTable, num, block.Hash().Bytes()); err != nil {
return fmt.Errorf("can't add block %d hash: %v", num, err) return fmt.Errorf("can't add block %d hash: %v", num, err)

View file

@ -377,7 +377,11 @@ func TestBlockReceiptStorage(t *testing.T) {
t.Fatalf("receipts returned when body was deleted: %v", rs) t.Fatalf("receipts returned when body was deleted: %v", rs)
} }
// Ensure that receipts without metadata can be returned without the block body too // Ensure that receipts without metadata can be returned without the block body too
if err := checkReceiptsRLP(ReadRawReceipts(db, hash, 0), receipts); err != nil { raw := ReadRawReceipts(db, hash, 0)
for _, r := range raw {
r.Bloom = types.CreateBloom(r)
}
if err := checkReceiptsRLP(raw, receipts); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Sanity check that body alone without the receipt is a full purge // Sanity check that body alone without the receipt is a full purge
@ -412,7 +416,7 @@ func checkReceiptsRLP(have, want types.Receipts) error {
func TestAncientStorage(t *testing.T) { func TestAncientStorage(t *testing.T) {
// Freezer style fast import the chain. // Freezer style fast import the chain.
frdir := t.TempDir() frdir := t.TempDir()
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false) db, err := Open(NewMemoryDatabase(), OpenOptions{Ancient: frdir})
if err != nil { if err != nil {
t.Fatalf("failed to create database with ancient backend") t.Fatalf("failed to create database with ancient backend")
} }
@ -439,7 +443,7 @@ func TestAncientStorage(t *testing.T) {
} }
// Write and verify the header in the database // Write and verify the header in the database
WriteAncientBlocks(db, []*types.Block{block}, []types.Receipts{nil}) WriteAncientBlocks(db, []*types.Block{block}, types.EncodeBlockReceiptLists([]types.Receipts{nil}))
if blob := ReadHeaderRLP(db, hash, number); len(blob) == 0 { if blob := ReadHeaderRLP(db, hash, number); len(blob) == 0 {
t.Fatalf("no header returned") t.Fatalf("no header returned")
@ -465,7 +469,7 @@ func TestAncientStorage(t *testing.T) {
} }
func TestWriteAncientHeaderChain(t *testing.T) { func TestWriteAncientHeaderChain(t *testing.T) {
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), t.TempDir(), "", false) db, err := Open(NewMemoryDatabase(), OpenOptions{Ancient: t.TempDir()})
if err != nil { if err != nil {
t.Fatalf("failed to create database with ancient backend") t.Fatalf("failed to create database with ancient backend")
} }
@ -582,7 +586,7 @@ func TestHashesInRange(t *testing.T) {
func BenchmarkWriteAncientBlocks(b *testing.B) { func BenchmarkWriteAncientBlocks(b *testing.B) {
// Open freezer database. // Open freezer database.
frdir := b.TempDir() frdir := b.TempDir()
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false) db, err := Open(NewMemoryDatabase(), OpenOptions{Ancient: frdir})
if err != nil { if err != nil {
b.Fatalf("failed to create database with ancient backend") b.Fatalf("failed to create database with ancient backend")
} }
@ -609,7 +613,7 @@ func BenchmarkWriteAncientBlocks(b *testing.B) {
blocks := allBlocks[i : i+length] blocks := allBlocks[i : i+length]
receipts := batchReceipts[:length] receipts := batchReceipts[:length]
writeSize, err := WriteAncientBlocks(db, blocks, receipts) writeSize, err := WriteAncientBlocks(db, blocks, types.EncodeBlockReceiptLists(receipts))
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -886,7 +890,7 @@ func TestHeadersRLPStorage(t *testing.T) {
// Have N headers in the freezer // Have N headers in the freezer
frdir := t.TempDir() frdir := t.TempDir()
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false) db, err := Open(NewMemoryDatabase(), OpenOptions{Ancient: frdir})
if err != nil { if err != nil {
t.Fatalf("failed to create database with ancient backend") t.Fatalf("failed to create database with ancient backend")
} }
@ -909,7 +913,7 @@ func TestHeadersRLPStorage(t *testing.T) {
} }
receipts := make([]types.Receipts, 100) receipts := make([]types.Receipts, 100)
// Write first half to ancients // Write first half to ancients
WriteAncientBlocks(db, chain[:50], receipts[:50]) WriteAncientBlocks(db, chain[:50], types.EncodeBlockReceiptLists(receipts[:50]))
// Write second half to db // Write second half to db
for i := 50; i < 100; i++ { for i := 50; i < 100; i++ {
WriteCanonicalHash(db, chain[i].Hash(), chain[i].NumberU64()) WriteCanonicalHash(db, chain[i].Hash(), chain[i].NumberU64())

View file

@ -446,7 +446,7 @@ type FilterMapsRange struct {
// database entry is not present, that is interpreted as a valid non-initialized // database entry is not present, that is interpreted as a valid non-initialized
// state and returns a blank range structure and no error. // state and returns a blank range structure and no error.
func ReadFilterMapsRange(db ethdb.KeyValueReader) (FilterMapsRange, bool, error) { func ReadFilterMapsRange(db ethdb.KeyValueReader) (FilterMapsRange, bool, error) {
if has, err := db.Has(filterMapsRangeKey); !has || err != nil { if has, err := db.Has(filterMapsRangeKey); err != nil || !has {
return FilterMapsRange{}, false, err return FilterMapsRange{}, false, err
} }
encRange, err := db.Get(filterMapsRangeKey) encRange, err := db.Get(filterMapsRangeKey)
@ -457,7 +457,8 @@ func ReadFilterMapsRange(db ethdb.KeyValueReader) (FilterMapsRange, bool, error)
if err := rlp.DecodeBytes(encRange, &fmRange); err != nil { if err := rlp.DecodeBytes(encRange, &fmRange); err != nil {
return FilterMapsRange{}, false, err return FilterMapsRange{}, false, err
} }
return fmRange, true, err
return fmRange, true, nil
} }
// WriteFilterMapsRange stores the filter maps range data. // WriteFilterMapsRange stores the filter maps range data.

View file

@ -258,13 +258,21 @@ func ReadStateHistory(db ethdb.AncientReaderOp, id uint64) ([]byte, []byte, []by
// WriteStateHistory writes the provided state history to database. Compute the // WriteStateHistory writes the provided state history to database. Compute the
// position of state history in freezer by minus one since the id of first state // position of state history in freezer by minus one since the id of first state
// history starts from one(zero for initial state). // history starts from one(zero for initial state).
func WriteStateHistory(db ethdb.AncientWriter, id uint64, meta []byte, accountIndex []byte, storageIndex []byte, accounts []byte, storages []byte) { func WriteStateHistory(db ethdb.AncientWriter, id uint64, meta []byte, accountIndex []byte, storageIndex []byte, accounts []byte, storages []byte) error {
db.ModifyAncients(func(op ethdb.AncientWriteOp) error { _, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
op.AppendRaw(stateHistoryMeta, id-1, meta) if err := op.AppendRaw(stateHistoryMeta, id-1, meta); err != nil {
op.AppendRaw(stateHistoryAccountIndex, id-1, accountIndex) return err
op.AppendRaw(stateHistoryStorageIndex, id-1, storageIndex) }
op.AppendRaw(stateHistoryAccountData, id-1, accounts) if err := op.AppendRaw(stateHistoryAccountIndex, id-1, accountIndex); err != nil {
op.AppendRaw(stateHistoryStorageData, id-1, storages) return err
return nil }
if err := op.AppendRaw(stateHistoryStorageIndex, id-1, storageIndex); err != nil {
return err
}
if err := op.AppendRaw(stateHistoryAccountData, id-1, accounts); err != nil {
return err
}
return op.AppendRaw(stateHistoryStorageData, id-1, storages)
}) })
return err
} }

View file

@ -18,7 +18,6 @@ package rawdb
import ( import (
"fmt" "fmt"
"sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -45,25 +44,6 @@ const HashScheme = "hash"
// on extra state diffs to survive deep reorg. // on extra state diffs to survive deep reorg.
const PathScheme = "path" const PathScheme = "path"
// hasher is used to compute the sha256 hash of the provided data.
type hasher struct{ sha crypto.KeccakState }
var hasherPool = sync.Pool{
New: func() interface{} { return &hasher{sha: crypto.NewKeccakState()} },
}
func newHasher() *hasher {
return hasherPool.Get().(*hasher)
}
func (h *hasher) hash(data []byte) common.Hash {
return crypto.HashData(h.sha, data)
}
func (h *hasher) release() {
hasherPool.Put(h)
}
// ReadAccountTrieNode retrieves the account trie node with the specified node path. // ReadAccountTrieNode retrieves the account trie node with the specified node path.
func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) []byte { func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) []byte {
data, _ := db.Get(accountTrieNodeKey(path)) data, _ := db.Get(accountTrieNodeKey(path))
@ -170,9 +150,7 @@ func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash c
if len(blob) == 0 { if len(blob) == 0 {
return false return false
} }
h := newHasher() return crypto.Keccak256Hash(blob) == hash // exists but not match
defer h.release()
return h.hash(blob) == hash // exists but not match
default: default:
panic(fmt.Sprintf("Unknown scheme %v", scheme)) panic(fmt.Sprintf("Unknown scheme %v", scheme))
} }
@ -194,9 +172,7 @@ func ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash
if len(blob) == 0 { if len(blob) == 0 {
return nil return nil
} }
h := newHasher() if crypto.Keccak256Hash(blob) != hash {
defer h.release()
if h.hash(blob) != hash {
return nil // exists but not match return nil // exists but not match
} }
return blob return blob

View file

@ -77,13 +77,6 @@ func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
} }
for _, c := range cases { for _, c := range cases {
for i := c.start; i < c.limit; i++ { for i := c.start; i < c.limit; i++ {
exist, err := db.HasAncient("a", uint64(i))
if err != nil {
t.Fatalf("Failed to check presence, %v", err)
}
if exist {
t.Fatalf("Item %d is already truncated", uint64(i))
}
_, err = db.Ancient("a", uint64(i)) _, err = db.Ancient("a", uint64(i))
if err == nil { if err == nil {
t.Fatal("Error is expected for non-existent item") t.Fatal("Error is expected for non-existent item")
@ -93,13 +86,6 @@ func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
// Test the items in range should be reachable // Test the items in range should be reachable
for i := 10; i < 90; i++ { for i := 10; i < 90; i++ {
exist, err := db.HasAncient("a", uint64(i))
if err != nil {
t.Fatalf("Failed to check presence, %v", err)
}
if !exist {
t.Fatalf("Item %d is missing", uint64(i))
}
blob, err := db.Ancient("a", uint64(i)) blob, err := db.Ancient("a", uint64(i))
if err != nil { if err != nil {
t.Fatalf("Failed to retrieve item, %v", err) t.Fatalf("Failed to retrieve item, %v", err)
@ -110,13 +96,6 @@ func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
} }
// Test the items in unknown table shouldn't be reachable // Test the items in unknown table shouldn't be reachable
exist, err := db.HasAncient("b", uint64(0))
if err != nil {
t.Fatalf("Failed to check presence, %v", err)
}
if exist {
t.Fatal("Item in unknown table shouldn't be found")
}
_, err = db.Ancient("b", uint64(0)) _, err = db.Ancient("b", uint64(0))
if err == nil { if err == nil {
t.Fatal("Error is expected for unknown table") t.Fatal("Error is expected for unknown table")

View file

@ -23,6 +23,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb/eradb"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -43,7 +44,10 @@ const (
// feature. The background thread will keep moving ancient chain segments from // feature. The background thread will keep moving ancient chain segments from
// key-value database to flat files for saving space on live database. // key-value database to flat files for saving space on live database.
type chainFreezer struct { type chainFreezer struct {
ethdb.AncientStore // Ancient store for storing cold chain segment ancients ethdb.AncientStore // Ancient store for storing cold chain segment
// Optional Era database used as a backup for the pruned chain.
eradb *eradb.Store
quit chan struct{} quit chan struct{}
wg sync.WaitGroup wg sync.WaitGroup
@ -56,23 +60,27 @@ type chainFreezer struct {
// state freezer (e.g. dev mode). // state freezer (e.g. dev mode).
// - if non-empty directory is given, initializes the regular file-based // - if non-empty directory is given, initializes the regular file-based
// state freezer. // state freezer.
func newChainFreezer(datadir string, namespace string, readonly bool) (*chainFreezer, error) { func newChainFreezer(datadir string, eraDir string, namespace string, readonly bool) (*chainFreezer, error) {
var (
err error
freezer ethdb.AncientStore
)
if datadir == "" { if datadir == "" {
freezer = NewMemoryFreezer(readonly, chainFreezerTableConfigs) return &chainFreezer{
} else { ancients: NewMemoryFreezer(readonly, chainFreezerTableConfigs),
freezer, err = NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerTableConfigs) quit: make(chan struct{}),
trigger: make(chan chan struct{}),
}, nil
} }
freezer, err := NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerTableConfigs)
if err != nil {
return nil, err
}
edb, err := eradb.New(resolveChainEraDir(datadir, eraDir))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &chainFreezer{ return &chainFreezer{
AncientStore: freezer, ancients: freezer,
quit: make(chan struct{}), eradb: edb,
trigger: make(chan chan struct{}), quit: make(chan struct{}),
trigger: make(chan chan struct{}),
}, nil }, nil
} }
@ -84,7 +92,11 @@ func (f *chainFreezer) Close() error {
close(f.quit) close(f.quit)
} }
f.wg.Wait() f.wg.Wait()
return f.AncientStore.Close()
if f.eradb != nil {
f.eradb.Close()
}
return f.ancients.Close()
} }
// readHeadNumber returns the number of chain head block. 0 is returned if the // readHeadNumber returns the number of chain head block. 0 is returned if the
@ -334,3 +346,75 @@ func (f *chainFreezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hash
}) })
return hashes, err return hashes, err
} }
// Ancient retrieves an ancient binary blob from the append-only immutable files.
func (f *chainFreezer) Ancient(kind string, number uint64) ([]byte, error) {
// Lookup the entry in the underlying ancient store, assuming that
// headers and hashes are always available.
if kind == ChainFreezerHeaderTable || kind == ChainFreezerHashTable {
return f.ancients.Ancient(kind, number)
}
tail, err := f.ancients.Tail()
if err != nil {
return nil, err
}
// Lookup the entry in the underlying ancient store if it's not pruned
if number >= tail {
return f.ancients.Ancient(kind, number)
}
// Lookup the entry in the optional era backend
if f.eradb == nil {
return nil, errOutOfBounds
}
switch kind {
case ChainFreezerBodiesTable:
return f.eradb.GetRawBody(number)
case ChainFreezerReceiptTable:
return f.eradb.GetRawReceipts(number)
}
return nil, errUnknownTable
}
// ReadAncients executes an operation while preventing mutations to the freezer,
// i.e. if fn performs multiple reads, they will be consistent with each other.
func (f *chainFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) {
if store, ok := f.ancients.(*Freezer); ok {
store.writeLock.Lock()
defer store.writeLock.Unlock()
}
return fn(f)
}
// Methods below are just pass-through to the underlying ancient store.
func (f *chainFreezer) Ancients() (uint64, error) {
return f.ancients.Ancients()
}
func (f *chainFreezer) Tail() (uint64, error) {
return f.ancients.Tail()
}
func (f *chainFreezer) AncientSize(kind string) (uint64, error) {
return f.ancients.AncientSize(kind)
}
func (f *chainFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
return f.ancients.AncientRange(kind, start, count, maxBytes)
}
func (f *chainFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (int64, error) {
return f.ancients.ModifyAncients(fn)
}
func (f *chainFreezer) TruncateHead(items uint64) (uint64, error) {
return f.ancients.TruncateHead(items)
}
func (f *chainFreezer) TruncateTail(items uint64) (uint64, error) {
return f.ancients.TruncateTail(items)
}
func (f *chainFreezer) SyncAncient() error {
return f.ancients.SyncAncient()
}

View file

@ -86,11 +86,6 @@ type nofreezedb struct {
ethdb.KeyValueStore ethdb.KeyValueStore
} }
// HasAncient returns an error as we don't have a backing chain freezer.
func (db *nofreezedb) HasAncient(kind string, number uint64) (bool, error) {
return false, errNotSupported
}
// Ancient returns an error as we don't have a backing chain freezer. // Ancient returns an error as we don't have a backing chain freezer.
func (db *nofreezedb) Ancient(kind string, number uint64) ([]byte, error) { func (db *nofreezedb) Ancient(kind string, number uint64) ([]byte, error) {
return nil, errNotSupported return nil, errNotSupported
@ -186,19 +181,49 @@ func resolveChainFreezerDir(ancient string) string {
return freezer return freezer
} }
// NewDatabaseWithFreezer creates a high level database on top of a given key- // resolveChainEraDir is a helper function which resolves the absolute path of era database.
// value data store with a freezer moving immutable chain segments into cold func resolveChainEraDir(chainFreezerDir string, era string) string {
// storage. The passed ancient indicates the path of root ancient directory switch {
// where the chain freezer can be opened. case era == "":
return filepath.Join(chainFreezerDir, "era")
case !filepath.IsAbs(era):
return filepath.Join(chainFreezerDir, era)
default:
return era
}
}
// NewDatabaseWithFreezer creates a high level database on top of a given key-value store.
// The passed ancient indicates the path of root ancient directory where the chain freezer
// can be opened.
//
// Deprecated: use Open.
func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace string, readonly bool) (ethdb.Database, error) { func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace string, readonly bool) (ethdb.Database, error) {
return Open(db, OpenOptions{
Ancient: ancient,
MetricsNamespace: namespace,
ReadOnly: readonly,
})
}
// OpenOptions specifies options for opening the database.
type OpenOptions struct {
Ancient string // ancients directory
Era string // era files directory
MetricsNamespace string // prefix added to freezer metric names
ReadOnly bool
}
// Open creates a high-level database wrapper for the given key-value store.
func Open(db ethdb.KeyValueStore, opts OpenOptions) (ethdb.Database, error) {
// Create the idle freezer instance. If the given ancient directory is empty, // Create the idle freezer instance. If the given ancient directory is empty,
// in-memory chain freezer is used (e.g. dev mode); otherwise the regular // in-memory chain freezer is used (e.g. dev mode); otherwise the regular
// file-based freezer is created. // file-based freezer is created.
chainFreezerDir := ancient chainFreezerDir := opts.Ancient
if chainFreezerDir != "" { if chainFreezerDir != "" {
chainFreezerDir = resolveChainFreezerDir(chainFreezerDir) chainFreezerDir = resolveChainFreezerDir(chainFreezerDir)
} }
frdb, err := newChainFreezer(chainFreezerDir, namespace, readonly) frdb, err := newChainFreezer(chainFreezerDir, opts.Era, opts.MetricsNamespace, opts.ReadOnly)
if err != nil { if err != nil {
printChainMetadata(db) printChainMetadata(db)
return nil, err return nil, err
@ -282,7 +307,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
} }
} }
// Freezer is consistent with the key-value database, permit combining the two // Freezer is consistent with the key-value database, permit combining the two
if !readonly { if !opts.ReadOnly {
frdb.wg.Add(1) frdb.wg.Add(1)
go func() { go func() {
frdb.freeze(db) frdb.freeze(db)
@ -290,7 +315,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
}() }()
} }
return &freezerdb{ return &freezerdb{
ancientRoot: ancient, ancientRoot: opts.Ancient,
KeyValueStore: db, KeyValueStore: db,
chainFreezer: frdb, chainFreezer: frdb,
}, nil }, nil

345
core/rawdb/eradb/eradb.go Normal file
View file

@ -0,0 +1,345 @@
// Copyright 2025 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 eradb implements a history backend using era1 files.
package eradb
import (
"bytes"
"errors"
"fmt"
"io/fs"
"path/filepath"
"sync"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
)
const openFileLimit = 64
var errClosed = errors.New("era store is closed")
// Store manages read access to a directory of era1 files.
// The getter methods are thread-safe.
type Store struct {
datadir string
// The mutex protects all remaining fields.
mu sync.Mutex
cond *sync.Cond
lru lru.BasicLRU[uint64, *fileCacheEntry]
opening map[uint64]*fileCacheEntry
closing bool
}
type fileCacheEntry struct {
refcount int // reference count. This is protected by Store.mu!
opened chan struct{} // signals opening of file has completed
file *era.Era // the file
err error // error from opening the file
}
type fileCacheStatus byte
const (
storeClosing fileCacheStatus = iota
fileIsNew
fileIsOpening
fileIsCached
)
// New opens the store directory.
func New(datadir string) (*Store, error) {
db := &Store{
datadir: datadir,
lru: lru.NewBasicLRU[uint64, *fileCacheEntry](openFileLimit),
opening: make(map[uint64]*fileCacheEntry),
}
db.cond = sync.NewCond(&db.mu)
log.Info("Opened Era store", "datadir", datadir)
return db, nil
}
// Close closes all open era1 files in the cache.
func (db *Store) Close() {
db.mu.Lock()
defer db.mu.Unlock()
// Prevent new cache additions.
db.closing = true
// Deref all active files. Since inactive files have a refcount of one, they will be
// closed right here and now after decrementing. Files which are currently being used
// have a refcount > 1 and will hit zero when their access finishes.
for _, epoch := range db.lru.Keys() {
entry, _ := db.lru.Peek(epoch)
if entry.derefAndClose(epoch) {
db.lru.Remove(epoch)
}
}
// Wait for all store access to finish.
for db.lru.Len() > 0 || len(db.opening) > 0 {
db.cond.Wait()
}
}
// GetRawBody returns the raw body for a given block number.
func (db *Store) GetRawBody(number uint64) ([]byte, error) {
epoch := number / uint64(era.MaxEra1Size)
entry := db.getEraByEpoch(epoch)
if entry.err != nil {
if errors.Is(entry.err, fs.ErrNotExist) {
return nil, nil
}
return nil, entry.err
}
defer db.doneWithFile(epoch, entry)
return entry.file.GetRawBodyByNumber(number)
}
// GetRawReceipts returns the raw receipts for a given block number.
func (db *Store) GetRawReceipts(number uint64) ([]byte, error) {
epoch := number / uint64(era.MaxEra1Size)
entry := db.getEraByEpoch(epoch)
if entry.err != nil {
if errors.Is(entry.err, fs.ErrNotExist) {
return nil, nil
}
return nil, entry.err
}
defer db.doneWithFile(epoch, entry)
data, err := entry.file.GetRawReceiptsByNumber(number)
if err != nil {
return nil, err
}
return convertReceipts(data)
}
// convertReceipts transforms an encoded block receipts list from the format
// used by era1 into the 'storage' format used by the go-ethereum ancients database.
func convertReceipts(input []byte) ([]byte, error) {
var (
out bytes.Buffer
enc = rlp.NewEncoderBuffer(&out)
)
blockListIter, err := rlp.NewListIterator(input)
if err != nil {
return nil, fmt.Errorf("invalid block receipts list: %v", err)
}
outerList := enc.List()
for i := 0; blockListIter.Next(); i++ {
kind, content, _, err := rlp.Split(blockListIter.Value())
if err != nil {
return nil, fmt.Errorf("receipt %d invalid: %v", i, err)
}
var receiptData []byte
switch kind {
case rlp.Byte:
return nil, fmt.Errorf("receipt %d is single byte", i)
case rlp.String:
// Typed receipt - skip type.
receiptData = content[1:]
case rlp.List:
// Legacy receipt
receiptData = blockListIter.Value()
}
// Convert data list.
// Input is [status, gas-used, bloom, logs]
// Output is [status, gas-used, logs], i.e. we need to skip the bloom.
dataIter, err := rlp.NewListIterator(receiptData)
if err != nil {
return nil, fmt.Errorf("receipt %d has invalid data: %v", i, err)
}
innerList := enc.List()
for field := 0; dataIter.Next(); field++ {
if field == 2 {
continue // skip bloom
}
enc.Write(dataIter.Value())
}
enc.ListEnd(innerList)
if dataIter.Err() != nil {
return nil, fmt.Errorf("receipt %d iterator error: %v", i, dataIter.Err())
}
}
enc.ListEnd(outerList)
if blockListIter.Err() != nil {
return nil, fmt.Errorf("block receipt list iterator error: %v", blockListIter.Err())
}
enc.Flush()
return out.Bytes(), nil
}
// getEraByEpoch opens an era file or gets it from the cache.
// The caller can freely access the returned entry's .file and .err
// db.doneWithFile must be called when it is done reading the file.
func (db *Store) getEraByEpoch(epoch uint64) *fileCacheEntry {
stat, entry := db.getCacheEntry(epoch)
switch stat {
case storeClosing:
return &fileCacheEntry{err: errClosed}
case fileIsNew:
// Open the file and put it into the cache.
e, err := db.openEraFile(epoch)
if err != nil {
db.fileFailedToOpen(epoch, entry, err)
} else {
db.fileOpened(epoch, entry, e)
}
close(entry.opened)
case fileIsOpening:
// Wait for open to finish.
<-entry.opened
case fileIsCached:
// Nothing to do.
default:
panic(fmt.Sprintf("invalid file state %d", stat))
}
return entry
}
// getCacheEntry gets an open era file from the cache.
func (db *Store) getCacheEntry(epoch uint64) (stat fileCacheStatus, entry *fileCacheEntry) {
db.mu.Lock()
defer db.mu.Unlock()
if db.closing {
return storeClosing, nil
}
if entry = db.opening[epoch]; entry != nil {
stat = fileIsOpening
} else if entry, _ = db.lru.Get(epoch); entry != nil {
stat = fileIsCached
} else {
// It's a new file, create an entry in the opening table. Note the entry is
// created with an initial refcount of one. We increment the count once more
// before returning, but the count will return to one when the file has been
// accessed. When the store is closed or the file gets evicted from the cache,
// refcount will be decreased by one, thus allowing it to hit zero.
entry = &fileCacheEntry{refcount: 1, opened: make(chan struct{})}
db.opening[epoch] = entry
stat = fileIsNew
}
entry.refcount++
return stat, entry
}
// fileOpened is called after an era file has been successfully opened.
func (db *Store) fileOpened(epoch uint64, entry *fileCacheEntry, file *era.Era) {
db.mu.Lock()
defer db.mu.Unlock()
delete(db.opening, epoch)
db.cond.Signal() // db.opening was modified
// The database may have been closed while opening the file. When that happens, we
// need to close the file here, since it isn't tracked by the LRU yet.
if db.closing {
entry.err = errClosed
file.Close()
return
}
// Add it to the LRU. This may evict an existing item, which we have to close.
entry.file = file
evictedEpoch, evictedEntry, _ := db.lru.Add3(epoch, entry)
if evictedEntry != nil {
evictedEntry.derefAndClose(evictedEpoch)
}
}
// fileFailedToOpen is called when an era file could not be opened.
func (db *Store) fileFailedToOpen(epoch uint64, entry *fileCacheEntry, err error) {
db.mu.Lock()
defer db.mu.Unlock()
delete(db.opening, epoch)
db.cond.Signal() // db.opening was modified
entry.err = err
}
func (db *Store) openEraFile(epoch uint64) (*era.Era, error) {
// File name scheme is <network>-<epoch>-<root>.
glob := fmt.Sprintf("*-%05d-*.era1", epoch)
matches, err := filepath.Glob(filepath.Join(db.datadir, glob))
if err != nil {
return nil, err
}
if len(matches) > 1 {
return nil, fmt.Errorf("multiple era1 files found for epoch %d", epoch)
}
if len(matches) == 0 {
return nil, fs.ErrNotExist
}
filename := matches[0]
e, err := era.Open(filename)
if err != nil {
return nil, err
}
// Sanity-check start block.
if e.Start()%uint64(era.MaxEra1Size) != 0 {
return nil, fmt.Errorf("pre-merge era1 file has invalid boundary. %d %% %d != 0", e.Start(), era.MaxEra1Size)
}
log.Debug("Opened era1 file", "epoch", epoch)
return e, nil
}
// doneWithFile signals that the caller has finished using a file.
// This decrements the refcount and ensures the file is closed by the last user.
func (db *Store) doneWithFile(epoch uint64, entry *fileCacheEntry) {
db.mu.Lock()
defer db.mu.Unlock()
if entry.err != nil {
return
}
if entry.derefAndClose(epoch) {
// Delete closed entry from LRU if it is still present.
if e, _ := db.lru.Peek(epoch); e == entry {
db.lru.Remove(epoch)
db.cond.Signal() // db.lru was modified
}
}
}
// derefAndClose decrements the reference counter and closes the file
// when it hits zero.
func (entry *fileCacheEntry) derefAndClose(epoch uint64) (closed bool) {
entry.refcount--
if entry.refcount > 0 {
return false
}
closeErr := entry.file.Close()
if closeErr == nil {
log.Debug("Closed era1 file", "epoch", epoch)
} else {
log.Warn("Error closing era1 file", "epoch", epoch, "err", closeErr)
}
return true
}

View file

@ -0,0 +1,103 @@
// Copyright 2025 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 eradb
import (
"sync"
"testing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestEraDatabase(t *testing.T) {
db, err := New("testdata")
require.NoError(t, err)
defer db.Close()
r, err := db.GetRawBody(175881)
require.NoError(t, err)
var body *types.Body
err = rlp.DecodeBytes(r, &body)
require.NoError(t, err)
require.NotNil(t, body, "block body not found")
assert.Equal(t, 3, len(body.Transactions))
r, err = db.GetRawReceipts(175881)
require.NoError(t, err)
var receipts []*types.ReceiptForStorage
err = rlp.DecodeBytes(r, &receipts)
require.NoError(t, err)
require.NotNil(t, receipts, "receipts not found")
assert.Equal(t, 3, len(receipts), "receipts length mismatch")
}
func TestEraDatabaseConcurrentOpen(t *testing.T) {
db, err := New("testdata")
require.NoError(t, err)
defer db.Close()
const N = 25
var wg sync.WaitGroup
wg.Add(N)
for range N {
go func() {
defer wg.Done()
r, err := db.GetRawBody(1024)
if err != nil {
t.Error("err:", err)
}
if len(r) == 0 {
t.Error("empty body")
}
}()
}
wg.Wait()
}
func TestEraDatabaseConcurrentOpenClose(t *testing.T) {
db, err := New("testdata")
require.NoError(t, err)
defer db.Close()
const N = 10
var wg sync.WaitGroup
wg.Add(N)
for range N {
go func() {
defer wg.Done()
r, err := db.GetRawBody(1024)
if err == errClosed {
return
}
if err != nil {
t.Error("err:", err)
}
if len(r) == 0 {
t.Error("empty body")
}
}()
}
wg.Add(1)
go func() {
defer wg.Done()
db.Close()
}()
wg.Wait()
}

Binary file not shown.

Binary file not shown.

View file

@ -172,10 +172,7 @@ func (f *Freezer) Close() error {
errs = append(errs, err) errs = append(errs, err)
} }
}) })
if errs != nil { return errors.Join(errs...)
return fmt.Errorf("%v", errs)
}
return nil
} }
// AncientDatadir returns the path of the ancient store. // AncientDatadir returns the path of the ancient store.
@ -183,15 +180,6 @@ func (f *Freezer) AncientDatadir() (string, error) {
return f.datadir, nil return f.datadir, nil
} }
// HasAncient returns an indicator whether the specified ancient data exists
// in the freezer.
func (f *Freezer) HasAncient(kind string, number uint64) (bool, error) {
if table := f.tables[kind]; table != nil {
return table.has(number), nil
}
return false, nil
}
// Ancient retrieves an ancient binary blob from the append-only immutable files. // Ancient retrieves an ancient binary blob from the append-only immutable files.
func (f *Freezer) Ancient(kind string, number uint64) ([]byte, error) { func (f *Freezer) Ancient(kind string, number uint64) ([]byte, error) {
if table := f.tables[kind]; table != nil { if table := f.tables[kind]; table != nil {

View file

@ -45,14 +45,6 @@ func newMemoryTable(name string, config freezerTableConfig) *memoryTable {
return &memoryTable{name: name, config: config} return &memoryTable{name: name, config: config}
} }
// has returns an indicator whether the specified data exists.
func (t *memoryTable) has(number uint64) bool {
t.lock.RLock()
defer t.lock.RUnlock()
return number >= t.offset && number < t.items
}
// retrieve retrieves multiple items in sequence, starting from the index 'start'. // retrieve retrieves multiple items in sequence, starting from the index 'start'.
// It will return: // It will return:
// - at most 'count' items, // - at most 'count' items,
@ -232,17 +224,6 @@ func NewMemoryFreezer(readonly bool, tableName map[string]freezerTableConfig) *M
} }
} }
// HasAncient returns an indicator whether the specified data exists.
func (f *MemoryFreezer) HasAncient(kind string, number uint64) (bool, error) {
f.lock.RLock()
defer f.lock.RUnlock()
if table := f.tables[kind]; table != nil {
return table.has(number), nil
}
return false, nil
}
// Ancient retrieves an ancient binary blob from the in-memory freezer. // Ancient retrieves an ancient binary blob from the in-memory freezer.
func (f *MemoryFreezer) Ancient(kind string, number uint64) ([]byte, error) { func (f *MemoryFreezer) Ancient(kind string, number uint64) ([]byte, error) {
f.lock.RLock() f.lock.RLock()

View file

@ -105,15 +105,6 @@ func (f *resettableFreezer) Close() error {
return f.freezer.Close() return f.freezer.Close()
} }
// HasAncient returns an indicator whether the specified ancient data exists
// in the freezer
func (f *resettableFreezer) HasAncient(kind string, number uint64) (bool, error) {
f.lock.RLock()
defer f.lock.RUnlock()
return f.freezer.HasAncient(kind, number)
}
// Ancient retrieves an ancient binary blob from the append-only immutable files. // Ancient retrieves an ancient binary blob from the append-only immutable files.
func (f *resettableFreezer) Ancient(kind string, number uint64) ([]byte, error) { func (f *resettableFreezer) Ancient(kind string, number uint64) ([]byte, error) {
f.lock.RLock() f.lock.RLock()

View file

@ -1106,12 +1106,6 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i
return output, sizes, nil return output, sizes, nil
} }
// has returns an indicator whether the specified number data is still accessible
// in the freezer table.
func (t *freezerTable) has(number uint64) bool {
return t.items.Load() > number && t.itemHidden.Load() <= number
}
// size returns the total data size in the freezer table. // size returns the total data size in the freezer table.
func (t *freezerTable) size() (uint64, error) { func (t *freezerTable) size() (uint64, error) {
t.lock.RLock() t.lock.RLock()

View file

@ -357,9 +357,6 @@ func checkAncientCount(t *testing.T, f *Freezer, kind string, n uint64) {
// Check at index n-1. // Check at index n-1.
if n > 0 { if n > 0 {
index := n - 1 index := n - 1
if ok, _ := f.HasAncient(kind, index); !ok {
t.Errorf("HasAncient(%q, %d) returned false unexpectedly", kind, index)
}
if _, err := f.Ancient(kind, index); err != nil { if _, err := f.Ancient(kind, index); err != nil {
t.Errorf("Ancient(%q, %d) returned unexpected error %q", kind, index, err) t.Errorf("Ancient(%q, %d) returned unexpected error %q", kind, index, err)
} }
@ -367,9 +364,6 @@ func checkAncientCount(t *testing.T, f *Freezer, kind string, n uint64) {
// Check at index n. // Check at index n.
index := n index := n
if ok, _ := f.HasAncient(kind, index); ok {
t.Errorf("HasAncient(%q, %d) returned true unexpectedly", kind, index)
}
if _, err := f.Ancient(kind, index); err == nil { if _, err := f.Ancient(kind, index); err == nil {
t.Errorf("Ancient(%q, %d) didn't return expected error", kind, index) t.Errorf("Ancient(%q, %d) didn't return expected error", kind, index)
} else if err != errOutOfBounds { } else if err != errOutOfBounds {

View file

@ -50,12 +50,6 @@ func (t *table) Get(key []byte) ([]byte, error) {
return t.db.Get(append([]byte(t.prefix), key...)) return t.db.Get(append([]byte(t.prefix), key...))
} }
// HasAncient is a noop passthrough that just forwards the request to the underlying
// database.
func (t *table) HasAncient(kind string, number uint64) (bool, error) {
return t.db.HasAncient(kind, number)
}
// Ancient is a noop passthrough that just forwards the request to the underlying // Ancient is a noop passthrough that just forwards the request to the underlying
// database. // database.
func (t *table) Ancient(kind string, number uint64) ([]byte, error) { func (t *table) Ancient(kind string, number uint64) ([]byte, error) {

View file

@ -18,6 +18,7 @@ package state
import ( import (
"maps" "maps"
gomath "math"
"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"
@ -92,97 +93,94 @@ func (ae *AccessEvents) Copy() *AccessEvents {
// AddAccount returns the gas to be charged for each of the currently cold // AddAccount returns the gas to be charged for each of the currently cold
// member fields of an account. // member fields of an account.
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 { func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool, availableGas uint64) uint64 {
var gas uint64 var gas uint64 // accumulate the consumed gas
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite) consumed, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite) if consumed < expected {
return expected
}
gas += consumed
consumed, expected = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas-consumed)
if consumed < expected {
return expected + gas
}
gas += expected
return gas return gas
} }
// MessageCallGas returns the gas to be charged for each of the currently // MessageCallGas returns the gas to be charged for each of the currently
// cold member fields of an account, that need to be touched when making a message // cold member fields of an account, that need to be touched when making a message
// call to that account. // call to that account.
func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 { func (ae *AccessEvents) MessageCallGas(destination common.Address, availableGas uint64) uint64 {
var gas uint64 _, expected := ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false) if expected == 0 {
return gas expected = params.WarmStorageReadCostEIP2929
}
return expected
} }
// ValueTransferGas returns the gas to be charged for each of the currently // ValueTransferGas returns the gas to be charged for each of the currently
// cold balance member fields of the caller and the callee accounts. // cold balance member fields of the caller and the callee accounts.
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 { func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address, availableGas uint64) uint64 {
var gas uint64 _, expected1 := ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true) if expected1 > availableGas {
gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true) return expected1
return gas }
_, expected2 := ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-expected1)
if expected1+expected2 == 0 {
return params.WarmStorageReadCostEIP2929
}
return expected1 + expected2
} }
// ContractCreatePreCheckGas charges access costs before // ContractCreatePreCheckGas charges access costs before
// a contract creation is initiated. It is just reads, because the // a contract creation is initiated. It is just reads, because the
// address collision is done before the transfer, and so no write // address collision is done before the transfer, and so no write
// are guaranteed to happen at this point. // are guaranteed to happen at this point.
func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address) uint64 { func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address, availableGas uint64) uint64 {
var gas uint64 consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false) _, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, availableGas-consumed)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false) return expected1 + expected2
return gas
} }
// ContractCreateInitGas returns the access gas costs for the initialization of // ContractCreateInitGas returns the access gas costs for the initialization of
// a contract creation. // a contract creation.
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address) uint64 { func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, availableGas uint64) (uint64, uint64) {
var gas uint64 var gas uint64
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true) consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true) gas += consumed
return gas consumed, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, availableGas-consumed)
gas += consumed
return gas, expected1 + expected2
} }
// AddTxOrigin adds the member fields of the sender account to the access event list, // AddTxOrigin adds the member fields of the sender account to the access event list,
// so that cold accesses are not charged, since they are covered by the 21000 gas. // so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) { func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) {
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BasicDataLeafKey, true) ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, gomath.MaxUint64)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeHashLeafKey, false) ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeHashLeafKey, false, gomath.MaxUint64)
} }
// AddTxDestination adds the member fields of the sender account to the access event list, // AddTxDestination adds the member fields of the sender account to the access event list,
// so that cold accesses are not charged, since they are covered by the 21000 gas. // so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) { func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue, doesntExist bool) {
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue) ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue, gomath.MaxUint64)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false) ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, doesntExist, gomath.MaxUint64)
} }
// SlotGas returns the amount of gas to be charged for a cold storage access. // SlotGas returns the amount of gas to be charged for a cold storage access.
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool) uint64 { func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
treeIndex, subIndex := utils.StorageIndex(slot.Bytes()) treeIndex, subIndex := utils.StorageIndex(slot.Bytes())
return ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) _, expected := ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas)
if expected == 0 && chargeWarmCosts {
expected = params.WarmStorageReadCostEIP2929
}
return expected
} }
// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the cold // touchAddressAndChargeGas adds any missing access event to the access event list, and returns the
// access cost to be charged, if need be. // consumed and required gas.
func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool, availableGas uint64) (uint64, uint64) {
stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := ae.touchAddress(addr, treeIndex, subIndex, isWrite)
var gas uint64
if stemRead {
gas += params.WitnessBranchReadCost
}
if selectorRead {
gas += params.WitnessChunkReadCost
}
if stemWrite {
gas += params.WitnessBranchWriteCost
}
if selectorWrite {
gas += params.WitnessChunkWriteCost
}
if selectorFill {
gas += params.WitnessChunkFillCost
}
return gas
}
// touchAddress adds any missing access event to the access event list.
func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) {
branchKey := newBranchAccessKey(addr, treeIndex) branchKey := newBranchAccessKey(addr, treeIndex)
chunkKey := newChunkAccessKey(branchKey, subIndex) chunkKey := newChunkAccessKey(branchKey, subIndex)
@ -190,11 +188,9 @@ func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int,
var branchRead, chunkRead bool var branchRead, chunkRead bool
if _, hasStem := ae.branches[branchKey]; !hasStem { if _, hasStem := ae.branches[branchKey]; !hasStem {
branchRead = true branchRead = true
ae.branches[branchKey] = AccessWitnessReadFlag
} }
if _, hasSelector := ae.chunks[chunkKey]; !hasSelector { if _, hasSelector := ae.chunks[chunkKey]; !hasSelector {
chunkRead = true chunkRead = true
ae.chunks[chunkKey] = AccessWitnessReadFlag
} }
// Write access. // Write access.
@ -202,17 +198,51 @@ func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int,
if isWrite { if isWrite {
if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 { if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 {
branchWrite = true branchWrite = true
ae.branches[branchKey] |= AccessWitnessWriteFlag
} }
chunkValue := ae.chunks[chunkKey] chunkValue := ae.chunks[chunkKey]
if (chunkValue & AccessWitnessWriteFlag) == 0 { if (chunkValue & AccessWitnessWriteFlag) == 0 {
chunkWrite = true chunkWrite = true
ae.chunks[chunkKey] |= AccessWitnessWriteFlag
} }
// TODO: charge chunk filling costs if the leaf was previously empty in the state
} }
return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill
var gas uint64
if branchRead {
gas += params.WitnessBranchReadCost
}
if chunkRead {
gas += params.WitnessChunkReadCost
}
if branchWrite {
gas += params.WitnessBranchWriteCost
}
if chunkWrite {
gas += params.WitnessChunkWriteCost
}
if chunkFill {
gas += params.WitnessChunkFillCost
}
if availableGas < gas {
// consumed != expected
return availableGas, gas
}
if branchRead {
ae.branches[branchKey] = AccessWitnessReadFlag
}
if branchWrite {
ae.branches[branchKey] |= AccessWitnessWriteFlag
}
if chunkRead {
ae.chunks[chunkKey] = AccessWitnessReadFlag
}
if chunkWrite {
ae.chunks[chunkKey] |= AccessWitnessWriteFlag
}
// consumed == expected
return gas, gas
} }
type branchAccessKey struct { type branchAccessKey struct {
@ -240,7 +270,7 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey {
} }
// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs // CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs
func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool, availableGas uint64) (uint64, uint64) {
// note that in the case where the copied code is outside the range of the // note that in the case where the copied code is outside the range of the
// contract code but touches the last leaf with contract code in it, // contract code but touches the last leaf with contract code in it,
// we don't include the last leaf of code in the AccessWitness. The // we don't include the last leaf of code in the AccessWitness. The
@ -248,7 +278,7 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC,
// is already in the AccessWitness so a stateless verifier can see that // is already in the AccessWitness so a stateless verifier can see that
// the code from the last leaf is not needed. // the code from the last leaf is not needed.
if (codeLen == 0 && size == 0) || startPC > codeLen { if (codeLen == 0 && size == 0) || startPC > codeLen {
return 0 return 0, 0
} }
endPC := startPC + size endPC := startPC + size
@ -263,22 +293,34 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC,
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ { for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256) treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
subIndex := byte((chunkNumber + 128) % 256) subIndex := byte((chunkNumber + 128) % 256)
gas := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite) consumed, expected := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, availableGas)
// did we OOG ?
if expected > consumed {
return statelessGasCharged + consumed, statelessGasCharged + expected
}
var overflow bool var overflow bool
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas) statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, consumed)
if overflow { if overflow {
panic("overflow when adding gas") panic("overflow when adding gas")
} }
availableGas -= consumed
} }
return statelessGasCharged return statelessGasCharged, statelessGasCharged
} }
// BasicDataGas adds the account's basic data to the accessed data, and returns the // BasicDataGas adds the account's basic data to the accessed data, and returns the
// amount of gas that it costs. // amount of gas that it costs.
// Note that an access in write mode implies an access in read mode, whereas an // Note that an access in write mode implies an access in read mode, whereas an
// access in read mode does not imply an access in write mode. // access in read mode does not imply an access in write mode.
func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool) uint64 { func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite) _, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
if expected == 0 && chargeWarmCosts {
if availableGas < params.WarmStorageReadCostEIP2929 {
return availableGas
}
expected = params.WarmStorageReadCostEIP2929
}
return expected
} }
// CodeHashGas adds the account's code hash to the accessed data, and returns the // CodeHashGas adds the account's code hash to the accessed data, and returns the
@ -286,6 +328,13 @@ func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool) uint64 {
// in write mode. If false, the charged gas corresponds to an access in read mode. // in write mode. If false, the charged gas corresponds to an access in read mode.
// Note that an access in write mode implies an access in read mode, whereas an access in // Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode. // read mode does not imply an access in write mode.
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 { func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite) _, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas)
if expected == 0 && chargeWarmCosts {
if availableGas < params.WarmStorageReadCostEIP2929 {
return availableGas
}
expected = params.WarmStorageReadCostEIP2929
}
return expected
} }

View file

@ -17,6 +17,7 @@
package state package state
import ( import (
"math"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -40,50 +41,50 @@ func TestAccountHeaderGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024)) ae := NewAccessEvents(utils.NewPointCache(1024))
// Check cold read cost // Check cold read cost
gas := ae.BasicDataGas(testAddr, false) gas := ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm read cost // Check warm read cost
gas = ae.BasicDataGas(testAddr, false) gas = ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
// Check cold read costs in the same group no longer incur the branch read cost // Check cold read costs in the same group no longer incur the branch read cost
gas = ae.CodeHashGas(testAddr, false) gas = ae.CodeHashGas(testAddr, false, math.MaxUint64, false)
if gas != params.WitnessChunkReadCost { if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
} }
// Check cold write cost // Check cold write cost
gas = ae.BasicDataGas(testAddr, true) gas = ae.BasicDataGas(testAddr, true, math.MaxUint64, false)
if want := params.WitnessBranchWriteCost + params.WitnessChunkWriteCost; gas != want { if want := params.WitnessBranchWriteCost + params.WitnessChunkWriteCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm write cost // Check warm write cost
gas = ae.BasicDataGas(testAddr, true) gas = ae.BasicDataGas(testAddr, true, math.MaxUint64, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
// Check a write without a read charges both read and write costs // Check a write without a read charges both read and write costs
gas = ae.BasicDataGas(testAddr2, true) gas = ae.BasicDataGas(testAddr2, true, math.MaxUint64, false)
if want := params.WitnessBranchReadCost + params.WitnessBranchWriteCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchReadCost + params.WitnessBranchWriteCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check that a write followed by a read charges nothing // Check that a write followed by a read charges nothing
gas = ae.BasicDataGas(testAddr2, false) gas = ae.BasicDataGas(testAddr2, false, math.MaxUint64, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
// Check that reading a slot from the account header only charges the // Check that reading a slot from the account header only charges the
// chunk read cost. // chunk read cost.
gas = ae.SlotGas(testAddr, common.Hash{}, false) gas = ae.SlotGas(testAddr, common.Hash{}, false, math.MaxUint64, false)
if gas != params.WitnessChunkReadCost { if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
} }
@ -100,13 +101,13 @@ func TestContractCreateInitGas(t *testing.T) {
} }
// Check cold read cost, without a value // Check cold read cost, without a value
gas := ae.ContractCreateInitGas(testAddr) gas, _ := ae.ContractCreateInitGas(testAddr, math.MaxUint64)
if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + 2*params.WitnessChunkWriteCost + 2*params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + 2*params.WitnessChunkWriteCost + 2*params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm read cost // Check warm read cost
gas = ae.ContractCreateInitGas(testAddr) gas, _ = ae.ContractCreateInitGas(testAddr, math.MaxUint64)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
@ -118,24 +119,24 @@ func TestMessageCallGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024)) ae := NewAccessEvents(utils.NewPointCache(1024))
// Check cold read cost, without a value // Check cold read cost, without a value
gas := ae.MessageCallGas(testAddr) gas := ae.MessageCallGas(testAddr, math.MaxUint64)
if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check that reading the basic data and code hash of the same account does not incur the branch read cost // Check that reading the basic data and code hash of the same account does not incur the branch read cost
gas = ae.BasicDataGas(testAddr, false) gas = ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
gas = ae.CodeHashGas(testAddr, false) gas = ae.CodeHashGas(testAddr, false, math.MaxUint64, false)
if gas != params.WitnessChunkReadCost { if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
// Check warm read cost // Check warm read cost
gas = ae.MessageCallGas(testAddr) gas = ae.MessageCallGas(testAddr, math.MaxUint64)
if gas != 0 { if gas != params.WarmStorageReadCostEIP2929 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WarmStorageReadCostEIP2929)
} }
} }

View file

@ -34,10 +34,10 @@ import (
const ( const (
// Number of codehash->size associations to keep. // Number of codehash->size associations to keep.
codeSizeCacheSize = 100000 codeSizeCacheSize = 1_000_000 // 4 megabytes in total
// Cache size granted for caching clean code. // Cache size granted for caching clean code.
codeCacheSize = 64 * 1024 * 1024 codeCacheSize = 256 * 1024 * 1024
// Number of address->curve point associations to keep. // Number of address->curve point associations to keep.
pointCacheSize = 4096 pointCacheSize = 4096
@ -175,26 +175,27 @@ func NewDatabaseForTesting() *CachingDB {
func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) { func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
var readers []StateReader var readers []StateReader
// Set up the state snapshot reader if available. This feature // Configure the state reader using the standalone snapshot in hash mode.
// is optional and may be partially useful if it's not fully // This reader offers improved performance but is optional and only
// generated. // partially useful if the snapshot is not fully generated.
if db.snap != nil { if db.TrieDB().Scheme() == rawdb.HashScheme && db.snap != nil {
// If standalone state snapshot is available (hash scheme),
// then construct the legacy snap reader.
snap := db.snap.Snapshot(stateRoot) snap := db.snap.Snapshot(stateRoot)
if snap != nil { if snap != nil {
readers = append(readers, newFlatReader(snap)) readers = append(readers, newFlatReader(snap))
} }
} else { }
// If standalone state snapshot is not available, try to construct // Configure the state reader using the path database in path mode.
// the state reader with database. // This reader offers improved performance but is optional and only
// partially useful if the snapshot data in path database is not
// fully generated.
if db.TrieDB().Scheme() == rawdb.PathScheme {
reader, err := db.triedb.StateReader(stateRoot) reader, err := db.triedb.StateReader(stateRoot)
if err == nil { if err == nil {
readers = append(readers, newFlatReader(reader)) // state reader is optional readers = append(readers, newFlatReader(reader))
} }
} }
// Set up the trie reader, which is expected to always be available // Configure the trie reader, which is expected to be available as the
// as the gatekeeper unless the state is corrupted. // gatekeeper unless the state is corrupted.
tr, err := newTrieReader(stateRoot, db.triedb, db.pointCache) tr, err := newTrieReader(stateRoot, db.triedb, db.pointCache)
if err != nil { if err != nil {
return nil, err return nil, err
@ -208,6 +209,15 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil
} }
// ReaderWithCache creates a state reader with internal local cache.
func (db *CachingDB) ReaderWithCache(stateRoot common.Hash) (Reader, error) {
reader, err := db.Reader(stateRoot)
if err != nil {
return nil, err
}
return newReaderWithCache(reader), nil
}
// OpenTrie opens the main account trie at a specific root hash. // OpenTrie opens the main account trie at a specific root hash.
func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) { func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
if db.triedb.IsVerkle() { if db.triedb.IsVerkle() {

View file

@ -408,6 +408,7 @@ func (ch storageChange) copy() journalEntry {
account: ch.account, account: ch.account,
key: ch.key, key: ch.key,
prevvalue: ch.prevvalue, prevvalue: ch.prevvalue,
origvalue: ch.origvalue,
} }
} }

View file

@ -18,6 +18,7 @@ package state
import ( import (
"errors" "errors"
"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/common/lru"
@ -51,6 +52,9 @@ type ContractCodeReader interface {
// 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
// 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.
// //
@ -70,6 +74,9 @@ type StateReader interface {
// Reader defines the interface for accessing accounts, storage slots and contract // Reader defines the interface for accessing accounts, storage slots and contract
// code associated with a specific state. // code associated with a specific state.
//
// Reader is assumed to be thread-safe and implementation must take care of the
// concurrency issue by themselves.
type Reader interface { type Reader interface {
ContractCodeReader ContractCodeReader
StateReader StateReader
@ -77,6 +84,8 @@ type Reader interface {
// cachingCodeReader implements ContractCodeReader, accessing contract code either in // cachingCodeReader implements ContractCodeReader, accessing contract code either in
// local key-value store or the shared code cache. // local key-value store or the shared code cache.
//
// cachingCodeReader is safe for concurrent access.
type cachingCodeReader struct { type cachingCodeReader struct {
db ethdb.KeyValueReader db ethdb.KeyValueReader
@ -123,18 +132,14 @@ func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash)
return len(code), nil return len(code), nil
} }
// flatReader wraps a database state reader. // flatReader wraps a database state reader and is safe for concurrent access.
type flatReader struct { type flatReader struct {
reader database.StateReader reader database.StateReader
buff crypto.KeccakState
} }
// newFlatReader constructs a state reader with on the given state root. // newFlatReader constructs a state reader with on the given state root.
func newFlatReader(reader database.StateReader) *flatReader { func newFlatReader(reader database.StateReader) *flatReader {
return &flatReader{ return &flatReader{reader: reader}
reader: reader,
buff: crypto.NewKeccakState(),
}
} }
// Account implements StateReader, retrieving the account specified by the address. // Account implements StateReader, retrieving the account specified by the address.
@ -144,7 +149,7 @@ func newFlatReader(reader database.StateReader) *flatReader {
// //
// The returned account might be nil if it's not existent. // The returned account might be nil if it's not existent.
func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) { func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
account, err := r.reader.Account(crypto.HashData(r.buff, addr.Bytes())) account, err := r.reader.Account(crypto.Keccak256Hash(addr.Bytes()))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -174,8 +179,8 @@ func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
// //
// The returned storage slot might be empty if it's not existent. // The returned storage slot might be empty if it's not existent.
func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) { func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
addrHash := crypto.HashData(r.buff, addr.Bytes()) addrHash := crypto.Keccak256Hash(addr.Bytes())
slotHash := crypto.HashData(r.buff, key.Bytes()) slotHash := crypto.Keccak256Hash(key.Bytes())
ret, err := r.reader.Storage(addrHash, slotHash) ret, err := r.reader.Storage(addrHash, slotHash)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
@ -196,13 +201,20 @@ func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash,
// trieReader implements the StateReader interface, providing functions to access // trieReader implements the StateReader interface, providing functions to access
// state from the referenced trie. // state from the referenced trie.
//
// trieReader is safe for concurrent read.
type trieReader struct { type trieReader struct {
root common.Hash // State root which uniquely represent a state root common.Hash // State root which uniquely represent a state
db *triedb.Database // Database for loading trie db *triedb.Database // Database for loading trie
buff crypto.KeccakState // Buffer for keccak256 hashing buff crypto.KeccakState // Buffer for keccak256 hashing
mainTrie Trie // Main trie, resolved in constructor
// Main trie, resolved in constructor. Note either the Merkle-Patricia-tree
// or Verkle-tree is not safe for concurrent read.
mainTrie Trie
subRoots map[common.Address]common.Hash // Set of storage roots, cached when the account is resolved subRoots map[common.Address]common.Hash // Set of storage roots, cached when the account is resolved
subTries map[common.Address]Trie // Group of storage tries, cached when it's resolved subTries map[common.Address]Trie // Group of storage tries, cached when it's resolved
lock sync.Mutex // Lock for protecting concurrent read
} }
// trieReader constructs a trie reader of the specific state. An error will be // trieReader constructs a trie reader of the specific state. An error will be
@ -230,11 +242,8 @@ func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCach
}, nil }, nil
} }
// Account implements StateReader, retrieving the account specified by the address. // account is the inner version of Account and assumes the r.lock is already held.
// func (r *trieReader) account(addr common.Address) (*types.StateAccount, error) {
// An error will be returned if the trie state is corrupted. An nil account
// will be returned if it's not existent in the trie.
func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
account, err := r.mainTrie.GetAccount(addr) account, err := r.mainTrie.GetAccount(addr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -247,12 +256,26 @@ func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
return account, nil return account, nil
} }
// Account implements StateReader, retrieving the account specified by the address.
//
// An error will be returned if the trie state is corrupted. An nil account
// will be returned if it's not existent in the trie.
func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
r.lock.Lock()
defer r.lock.Unlock()
return r.account(addr)
}
// Storage implements StateReader, retrieving the storage slot specified by the // Storage implements StateReader, retrieving the storage slot specified by the
// address and slot key. // address and slot key.
// //
// An error will be returned if the trie state is corrupted. An empty storage // An error will be returned if the trie state is corrupted. An empty storage
// slot will be returned if it's not existent in the trie. // slot will be returned if it's not existent in the trie.
func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) { func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
r.lock.Lock()
defer r.lock.Unlock()
var ( var (
tr Trie tr Trie
found bool found bool
@ -268,7 +291,7 @@ func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash,
// The storage slot is accessed without account caching. It's unexpected // The storage slot is accessed without account caching. It's unexpected
// behavior but try to resolve the account first anyway. // behavior but try to resolve the account first anyway.
if !ok { if !ok {
_, err := r.Account(addr) _, err := r.account(addr)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
@ -293,6 +316,9 @@ func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash,
// multiStateReader is the aggregation of a list of StateReader interface, // multiStateReader is the aggregation of a list of StateReader interface,
// providing state access by leveraging all readers. The checking priority // providing state access by leveraging all readers. The checking priority
// is determined by the position in the reader list. // is determined by the position in the reader list.
//
// multiStateReader is safe for concurrent read and assumes all underlying
// readers are thread-safe as well.
type multiStateReader struct { type multiStateReader struct {
readers []StateReader // List of state readers, sorted by checking priority readers []StateReader // List of state readers, sorted by checking priority
} }
@ -358,3 +384,95 @@ func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
StateReader: stateReader, StateReader: stateReader,
} }
} }
// readerWithCache is a wrapper around Reader that maintains additional state caches
// to support concurrent state access.
type readerWithCache struct {
Reader // safe for concurrent read
// Previously resolved state entries.
accounts map[common.Address]*types.StateAccount
accountLock sync.RWMutex
// List of storage buckets, each of which is thread-safe.
// This reader is typically used in scenarios requiring concurrent
// access to storage. Using multiple buckets helps mitigate
// the overhead caused by locking.
storageBuckets [16]struct {
lock sync.RWMutex
storages map[common.Address]map[common.Hash]common.Hash
}
}
// newReaderWithCache constructs the reader with local cache.
func newReaderWithCache(reader Reader) *readerWithCache {
r := &readerWithCache{
Reader: reader,
accounts: make(map[common.Address]*types.StateAccount),
}
for i := range r.storageBuckets {
r.storageBuckets[i].storages = make(map[common.Address]map[common.Hash]common.Hash)
}
return r
}
// Account implements StateReader, retrieving the account specified by the address.
// 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.
func (r *readerWithCache) Account(addr common.Address) (*types.StateAccount, error) {
// Try to resolve the requested account in the local cache
r.accountLock.RLock()
acct, ok := r.accounts[addr]
r.accountLock.RUnlock()
if ok {
return acct, nil
}
// Try to resolve the requested account from the underlying reader
acct, err := r.Reader.Account(addr)
if err != nil {
return nil, err
}
r.accountLock.Lock()
r.accounts[addr] = acct
r.accountLock.Unlock()
return acct, nil
}
// Storage implements StateReader, retrieving the storage slot specified by the
// address and slot key. The returned storage slot might be empty if it's not
// existent.
//
// An error will be returned if the state is corrupted in the underlying reader.
func (r *readerWithCache) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
var (
value common.Hash
ok bool
bucket = &r.storageBuckets[addr[0]&0x0f]
)
// Try to resolve the requested storage slot in the local cache
bucket.lock.RLock()
slots, ok := bucket.storages[addr]
if ok {
value, ok = slots[slot]
}
bucket.lock.RUnlock()
if ok {
return value, nil
}
// Try to resolve the requested storage slot from the underlying reader
value, err := r.Reader.Storage(addr, slot)
if err != nil {
return common.Hash{}, err
}
bucket.lock.Lock()
slots, ok = bucket.storages[addr]
if !ok {
slots = make(map[common.Hash]common.Hash)
bucket.storages[addr] = slots
}
slots[slot] = value
bucket.lock.Unlock()
return value, nil
}

View file

@ -166,7 +166,9 @@ func newHelper(scheme string) *testHelper {
diskdb := rawdb.NewMemoryDatabase() diskdb := rawdb.NewMemoryDatabase()
config := &triedb.Config{} config := &triedb.Config{}
if scheme == rawdb.PathScheme { if scheme == rawdb.PathScheme {
config.PathDB = &pathdb.Config{} // disable caching config.PathDB = &pathdb.Config{
SnapshotNoBuild: true,
} // disable caching
} else { } else {
config.HashDB = &hashdb.Config{} // disable caching config.HashDB = &hashdb.Config{} // disable caching
} }

View file

@ -159,11 +159,17 @@ type StateDB struct {
// New creates a new state from a given trie. // New creates a new state from a given trie.
func New(root common.Hash, db Database) (*StateDB, error) { func New(root common.Hash, db Database) (*StateDB, error) {
tr, err := db.OpenTrie(root) reader, err := db.Reader(root)
if err != nil { if err != nil {
return nil, err return nil, err
} }
reader, err := db.Reader(root) return NewWithReader(root, db, reader)
}
// NewWithReader creates a new state for the specified state root. Unlike New,
// this function accepts an additional Reader which is bound to the given root.
func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, error) {
tr, err := db.OpenTrie(root)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -292,7 +298,7 @@ func (s *StateDB) SubRefund(gas uint64) {
} }
// Exist reports whether the given account address exists in the state. // Exist reports whether the given account address exists in the state.
// Notably this also returns true for self-destructed accounts. // Notably this also returns true for self-destructed accounts within the current transaction.
func (s *StateDB) Exist(addr common.Address) bool { func (s *StateDB) Exist(addr common.Address) bool {
return s.getStateObject(addr) != nil return s.getStateObject(addr) != nil
} }
@ -392,6 +398,12 @@ func (s *StateDB) Database() Database {
return s.db return s.db
} }
// Reader retrieves the low level database reader supporting the
// lower level operations.
func (s *StateDB) Reader() Reader {
return s.reader
}
func (s *StateDB) HasSelfDestructed(addr common.Address) bool { func (s *StateDB) HasSelfDestructed(addr common.Address) bool {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if stateObject != nil { if stateObject != nil {
@ -550,9 +562,8 @@ func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common
// updateStateObject writes the given object to the trie. // updateStateObject writes the given object to the trie.
func (s *StateDB) updateStateObject(obj *stateObject) { func (s *StateDB) updateStateObject(obj *stateObject) {
// Encode the account and update the account trie // Encode the account and update the account trie
addr := obj.Address() if err := s.trie.UpdateAccount(obj.Address(), &obj.data, len(obj.code)); err != nil {
if err := s.trie.UpdateAccount(addr, &obj.data, len(obj.code)); err != nil { s.setError(fmt.Errorf("updateStateObject (%x) error: %v", obj.Address(), err))
s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err))
} }
if obj.dirtyCode { if obj.dirtyCode {
s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code) s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code)
@ -650,11 +661,10 @@ func (s *StateDB) CreateContract(addr common.Address) {
// Snapshots of the copied state cannot be applied to the copy. // Snapshots of the copied state cannot be applied to the copy.
func (s *StateDB) Copy() *StateDB { func (s *StateDB) Copy() *StateDB {
// Copy all the basic fields, initialize the memory ones // Copy all the basic fields, initialize the memory ones
reader, _ := s.db.Reader(s.originalRoot) // impossible to fail
state := &StateDB{ state := &StateDB{
db: s.db, db: s.db,
trie: mustCopyTrie(s.trie), trie: mustCopyTrie(s.trie),
reader: reader, reader: s.reader,
originalRoot: s.originalRoot, originalRoot: s.originalRoot,
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)), stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
stateObjectsDestruct: make(map[common.Address]*stateObject, len(s.stateObjectsDestruct)), stateObjectsDestruct: make(map[common.Address]*stateObject, len(s.stateObjectsDestruct)),

View file

@ -979,7 +979,8 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
) )
if scheme == rawdb.PathScheme { if scheme == rawdb.PathScheme {
tdb = triedb.NewDatabase(memDb, &triedb.Config{PathDB: &pathdb.Config{ tdb = triedb.NewDatabase(memDb, &triedb.Config{PathDB: &pathdb.Config{
CleanCacheSize: 0, TrieCleanSize: 0,
StateCleanSize: 0,
WriteBufferSize: 0, WriteBufferSize: 0,
}}) // disable caching }}) // disable caching
} else { } else {

View file

@ -17,17 +17,22 @@
package core package core
import ( import (
"bytes"
"runtime"
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/common"
"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/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"golang.org/x/sync/errgroup"
) )
// statePrefetcher is a basic Prefetcher, which blindly executes a block on top // statePrefetcher is a basic Prefetcher that executes transactions from a block
// of an arbitrary state with the goal of prefetching potentially useful state // on top of the parent state, aiming to prefetch potentially useful state data
// data from disk before the main block processor start executing. // from disk. Transactions are executed in parallel to fully leverage the
// SSD's read performance.
type statePrefetcher struct { type statePrefetcher struct {
config *params.ChainConfig // Chain configuration options config *params.ChainConfig // Chain configuration options
chain *HeaderChain // Canonical block chain chain *HeaderChain // Canonical block chain
@ -43,41 +48,81 @@ func newStatePrefetcher(config *params.ChainConfig, chain *HeaderChain) *statePr
// Prefetch processes the state changes according to the Ethereum rules by running // Prefetch processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb, but any changes are discarded. The // the transaction messages using the statedb, but any changes are discarded. The
// only goal is to pre-cache transaction signatures and state trie nodes. // only goal is to warm the state caches.
func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool) { func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool) {
var ( var (
header = block.Header() fails atomic.Int64
gaspool = new(GasPool).AddGas(block.GasLimit()) header = block.Header()
blockContext = NewEVMBlockContext(header, p.chain, nil) signer = types.MakeSigner(p.config, header.Number, header.Time)
evm = vm.NewEVM(blockContext, statedb, p.config, cfg) workers errgroup.Group
signer = types.MakeSigner(p.config, header.Number, header.Time) reader = statedb.Reader()
) )
// Iterate over and process the individual transactions workers.SetLimit(runtime.NumCPU() / 2)
byzantium := p.config.IsByzantium(block.Number())
for i, tx := range block.Transactions() {
// If block precaching was interrupted, abort
if interrupt != nil && interrupt.Load() {
return
}
// Convert the transaction into an executable message and pre-cache its sender
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
if err != nil {
return // Also invalid block, bail out
}
statedb.SetTxContext(tx.Hash(), i)
// We attempt to apply a transaction. The goal is not to execute // Iterate over and process the individual transactions
// the transaction successfully, rather to warm up touched data slots. for i, tx := range block.Transactions() {
if _, err := ApplyMessage(evm, msg, gaspool); err != nil { stateCpy := statedb.Copy() // closure
return // Ugh, something went horribly wrong, bail out workers.Go(func() error {
} // If block precaching was interrupted, abort
// If we're pre-byzantium, pre-load trie nodes for the intermediate root if interrupt != nil && interrupt.Load() {
if !byzantium { return nil
statedb.IntermediateRoot(true) }
} // Preload the touched accounts and storage slots in advance
} sender, err := types.Sender(signer, tx)
// If were post-byzantium, pre-load trie nodes for the final root hash if err != nil {
if byzantium { fails.Add(1)
statedb.IntermediateRoot(true) return nil
}
reader.Account(sender)
if tx.To() != nil {
account, _ := reader.Account(*tx.To())
// Preload the contract code if the destination has non-empty code
if account != nil && !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
reader.Code(*tx.To(), common.BytesToHash(account.CodeHash))
}
}
for _, list := range tx.AccessList() {
reader.Account(list.Address)
if len(list.StorageKeys) > 0 {
for _, slot := range list.StorageKeys {
reader.Storage(list.Address, slot)
}
}
}
// Execute the message to preload the implicit touched states
evm := vm.NewEVM(NewEVMBlockContext(header, p.chain, nil), stateCpy, p.config, cfg)
// Convert the transaction into an executable message and pre-cache its sender
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
if err != nil {
fails.Add(1)
return nil // Also invalid block, bail out
}
// Disable the nonce check
msg.SkipNonceChecks = true
stateCpy.SetTxContext(tx.Hash(), i)
// We attempt to apply a transaction. The goal is not to execute
// the transaction successfully, rather to warm up touched data slots.
if _, err := ApplyMessage(evm, msg, new(GasPool).AddGas(block.GasLimit())); err != nil {
fails.Add(1)
return nil // Ugh, something went horribly wrong, bail out
}
// Pre-load trie nodes for the intermediate root.
//
// This operation incurs significant memory allocations due to
// trie hashing and node decoding. TODO(rjl493456442): investigate
// ways to mitigate this overhead.
stateCpy.IntermediateRoot(true)
return nil
})
} }
workers.Wait()
blockPrefetchTxsValidMeter.Mark(int64(len(block.Transactions())) - fails.Load())
blockPrefetchTxsInvalidMeter.Mark(fails.Load())
return
} }

View file

@ -159,7 +159,9 @@ type Message struct {
// When SkipNonceChecks is true, the message nonce is not checked against the // When SkipNonceChecks is true, the message nonce is not checked against the
// account nonce in state. // account nonce in state.
// This field will be set to true for operations like RPC eth_call. //
// This field will be set to true for operations like RPC eth_call
// or the state prefetching.
SkipNonceChecks bool SkipNonceChecks bool
// When SkipFromEOACheck is true, the message sender is not checked to be an EOA. // When SkipFromEOACheck is true, the message sender is not checked to be an EOA.
@ -455,7 +457,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
st.evm.AccessEvents.AddTxOrigin(msg.From) st.evm.AccessEvents.AddTxOrigin(msg.From)
if targetAddr := msg.To; targetAddr != nil { if targetAddr := msg.To; targetAddr != nil {
st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0) st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0, !st.state.Exist(*targetAddr))
} }
} }
@ -550,7 +552,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// add the coinbase to the witness iff the fee is greater than 0 // add the coinbase to the witness iff the fee is greater than 0
if rules.IsEIP4762 && fee.Sign() != 0 { if rules.IsEIP4762 && fee.Sign() != 0 {
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true) st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64)
} }
} }

View file

@ -116,8 +116,8 @@ func TestTxIndexer(t *testing.T) {
}, },
} }
for _, c := range cases { for _, c := range cases {
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...)) rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...)))
// Index the initial blocks from ancient store // Index the initial blocks from ancient store
indexer := &txIndexer{ indexer := &txIndexer{
@ -235,8 +235,9 @@ func TestTxIndexerRepair(t *testing.T) {
}, },
} }
for _, c := range cases { for _, c := range cases {
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...)) encReceipts := types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...))
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), encReceipts)
// Index the initial blocks from ancient store // Index the initial blocks from ancient store
indexer := &txIndexer{ indexer := &txIndexer{
@ -425,8 +426,9 @@ func TestTxIndexerReport(t *testing.T) {
}, },
} }
for _, c := range cases { for _, c := range cases {
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...)) encReceipts := types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...))
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), encReceipts)
// Index the initial blocks from ancient store // Index the initial blocks from ancient store
indexer := &txIndexer{ indexer := &txIndexer{

View file

@ -62,6 +62,12 @@ const (
// limit can never hurt. // limit can never hurt.
txMaxSize = 1024 * 1024 txMaxSize = 1024 * 1024
// maxBlobsPerTx is the maximum number of blobs that a single transaction can
// carry. We choose a smaller limit than the protocol-permitted MaxBlobsPerBlock
// in order to ensure network and txpool stability.
// Note: if you increase this, validation will fail on txMaxSize.
maxBlobsPerTx = 7
// maxTxsPerAccount is the maximum number of blob transactions admitted from // maxTxsPerAccount is the maximum number of blob transactions admitted from
// a single account. The limit is enforced to minimize the DoS potential of // a single account. The limit is enforced to minimize the DoS potential of
// a private tx cancelling publicly propagated blobs. // a private tx cancelling publicly propagated blobs.
@ -1095,10 +1101,11 @@ func (p *BlobPool) SetGasTip(tip *big.Int) {
// and does not require the pool mutex to be held. // and does not require the pool mutex to be held.
func (p *BlobPool) ValidateTxBasics(tx *types.Transaction) error { func (p *BlobPool) ValidateTxBasics(tx *types.Transaction) error {
opts := &txpool.ValidationOptions{ opts := &txpool.ValidationOptions{
Config: p.chain.Config(), Config: p.chain.Config(),
Accept: 1 << types.BlobTxType, Accept: 1 << types.BlobTxType,
MaxSize: txMaxSize, MaxSize: txMaxSize,
MinTip: p.gasTip.ToBig(), MinTip: p.gasTip.ToBig(),
MaxBlobCount: maxBlobsPerTx,
} }
return txpool.ValidateTransaction(tx, p.head, p.signer, opts) return txpool.ValidateTransaction(tx, p.head, p.signer, opts)
} }

View file

@ -1142,6 +1142,65 @@ func TestChangingSlotterSize(t *testing.T) {
} }
} }
// TestBlobCountLimit tests the blobpool enforced limits on the max blob count.
func TestBlobCountLimit(t *testing.T) {
var (
key1, _ = crypto.GenerateKey()
key2, _ = crypto.GenerateKey()
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
)
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.Commit(0, true, false)
// Make Prague-enabled custom chain config.
cancunTime := uint64(0)
pragueTime := uint64(0)
config := &params.ChainConfig{
ChainID: big.NewInt(1),
LondonBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
CancunTime: &cancunTime,
PragueTime: &pragueTime,
BlobScheduleConfig: &params.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Prague: params.DefaultPragueBlobConfig,
},
}
chain := &testBlockChain{
config: config,
basefee: uint256.NewInt(1050),
blobfee: uint256.NewInt(105),
statedb: statedb,
}
pool := New(Config{Datadir: t.TempDir()}, chain, nil)
if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil {
t.Fatalf("failed to create blob pool: %v", err)
}
// Attempt to add transactions.
var (
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 7, key1)
tx2 = makeMultiBlobTx(0, 1, 800, 70, 8, key2)
)
errs := pool.Add([]*types.Transaction{tx1, tx2}, true)
// Check that first succeeds second fails.
if errs[0] != nil {
t.Fatalf("expected tx with 7 blobs to succeed")
}
if !errors.Is(errs[1], txpool.ErrTxBlobLimitExceeded) {
t.Fatalf("expected tx with 8 blobs to fail, got: %v", errs[1])
}
verifyPoolInternals(t, pool)
pool.Close()
}
// Tests that adding transaction will correctly store it in the persistent store // Tests that adding transaction will correctly store it in the persistent store
// and update all the indices. // and update all the indices.
// //

View file

@ -58,6 +58,10 @@ var (
// making the transaction invalid, rather a DOS protection. // making the transaction invalid, rather a DOS protection.
ErrOversizedData = errors.New("oversized data") ErrOversizedData = errors.New("oversized data")
// ErrTxBlobLimitExceeded is returned if a transaction would exceed the number
// of blobs allowed by blobpool.
ErrTxBlobLimitExceeded = errors.New("transaction blob limit exceeded")
// ErrAlreadyReserved is returned if the sender address has a pending transaction // ErrAlreadyReserved is returned if the sender address has a pending transaction
// in a different subpool. For example, this error is returned in response to any // in a different subpool. For example, this error is returned in response to any
// input transaction of non-blob type when a blob transaction from this sender // input transaction of non-blob type when a blob transaction from this sender

View file

@ -108,11 +108,33 @@ func pricedTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ec
return tx return tx
} }
func pricedDataTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ecdsa.PrivateKey, bytes uint64) *types.Transaction { // pricedDataTransaction generates a signed transaction with fixed-size data,
data := make([]byte, bytes) // and ensures that the resulting signature components (r and s) are exactly 32 bytes each,
crand.Read(data) // producing transactions with deterministic size.
//
// This avoids variability in transaction size caused by leading zeros being omitted in
// RLP encoding of r/s. Since r and s are derived from ECDSA, they occasionally have leading
// zeros and thus can be shorter than 32 bytes.
//
// For example:
//
// r: 0 leading zeros, bytesSize: 32, bytes: [221 ... 101]
// s: 1 leading zeros, bytesSize: 31, bytes: [0 75 ... 47]
func pricedDataTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ecdsa.PrivateKey, dataBytes uint64) *types.Transaction {
var tx *types.Transaction
tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(0), gaslimit, gasprice, data), types.HomesteadSigner{}, key) // 10 attempts is statistically sufficient since leading zeros in ECDSA signatures are rare and randomly distributed.
var retryTimes = 10
for i := 0; i < retryTimes; i++ {
data := make([]byte, dataBytes)
crand.Read(data)
tx, _ = types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(0), gaslimit, gasprice, data), types.HomesteadSigner{}, key)
_, r, s := tx.RawSignatureValues()
if len(r.Bytes()) == 32 && len(s.Bytes()) == 32 {
break
}
}
return tx return tx
} }
@ -1239,7 +1261,7 @@ func TestAllowedTxSize(t *testing.T) {
const largeDataLength = txMaxSize - 200 // enough to have a 5 bytes RLP encoding of the data length number const largeDataLength = txMaxSize - 200 // enough to have a 5 bytes RLP encoding of the data length number
txWithLargeData := pricedDataTransaction(0, pool.currentHead.Load().GasLimit, big.NewInt(1), key, largeDataLength) txWithLargeData := pricedDataTransaction(0, pool.currentHead.Load().GasLimit, big.NewInt(1), key, largeDataLength)
maxTxLengthWithoutData := txWithLargeData.Size() - largeDataLength // 103 bytes maxTxLengthWithoutData := txWithLargeData.Size() - largeDataLength // 103 bytes
maxTxDataLength := txMaxSize - maxTxLengthWithoutData // 131072 - 103 = 130953 bytes maxTxDataLength := txMaxSize - maxTxLengthWithoutData // 131072 - 103 = 130969 bytes
// Try adding a transaction with maximal allowed size // Try adding a transaction with maximal allowed size
tx := pricedDataTransaction(0, pool.currentHead.Load().GasLimit, big.NewInt(1), key, maxTxDataLength) tx := pricedDataTransaction(0, pool.currentHead.Load().GasLimit, big.NewInt(1), key, maxTxDataLength)

View file

@ -42,9 +42,10 @@ var (
type ValidationOptions struct { type ValidationOptions struct {
Config *params.ChainConfig // Chain configuration to selectively validate based on current fork rules Config *params.ChainConfig // Chain configuration to selectively validate based on current fork rules
Accept uint8 // Bitmap of transaction types that should be accepted for the calling pool Accept uint8 // Bitmap of transaction types that should be accepted for the calling pool
MaxSize uint64 // Maximum size of a transaction that the caller can meaningfully handle MaxSize uint64 // Maximum size of a transaction that the caller can meaningfully handle
MinTip *big.Int // Minimum gas tip needed to allow a transaction into the caller pool MaxBlobCount int // Maximum number of blobs allowed per transaction
MinTip *big.Int // Minimum gas tip needed to allow a transaction into the caller pool
} }
// ValidationFunction is an method type which the pools use to perform the tx-validations which do not // ValidationFunction is an method type which the pools use to perform the tx-validations which do not
@ -63,6 +64,9 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
if opts.Accept&(1<<tx.Type()) == 0 { if opts.Accept&(1<<tx.Type()) == 0 {
return fmt.Errorf("%w: tx type %v not supported by this pool", core.ErrTxTypeNotSupported, tx.Type()) return fmt.Errorf("%w: tx type %v not supported by this pool", core.ErrTxTypeNotSupported, tx.Type())
} }
if blobCount := len(tx.BlobHashes()); blobCount > opts.MaxBlobCount {
return fmt.Errorf("%w: blob count %v, limit %v", ErrTxBlobLimitExceeded, blobCount, opts.MaxBlobCount)
}
// Before performing any expensive validations, sanity check that the tx is // Before performing any expensive validations, sanity check that the tx is
// smaller than the maximum limit the pool can meaningfully handle // smaller than the maximum limit the pool can meaningfully handle
if tx.Size() > opts.MaxSize { if tx.Size() > opts.MaxSize {

View file

@ -38,17 +38,13 @@ type Account struct {
Storage map[common.Hash]common.Hash `json:"storage,omitempty"` Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
Balance *big.Int `json:"balance" gencodec:"required"` Balance *big.Int `json:"balance" gencodec:"required"`
Nonce uint64 `json:"nonce,omitempty"` Nonce uint64 `json:"nonce,omitempty"`
// used in tests
PrivateKey []byte `json:"secretKey,omitempty"`
} }
type accountMarshaling struct { type accountMarshaling struct {
Code hexutil.Bytes Code hexutil.Bytes
Balance *math.HexOrDecimal256 Balance *math.HexOrDecimal256
Nonce math.HexOrDecimal64 Nonce math.HexOrDecimal64
Storage map[storageJSON]storageJSON Storage map[storageJSON]storageJSON
PrivateKey hexutil.Bytes
} }
// storageJSON represents a 256 bit byte array, but allows less than 256 bits when // storageJSON represents a 256 bit byte array, but allows less than 256 bits when

View file

@ -59,11 +59,12 @@ func (b *Bloom) SetBytes(d []byte) {
// Add adds d to the filter. Future calls of Test(d) will return true. // Add adds d to the filter. Future calls of Test(d) will return true.
func (b *Bloom) Add(d []byte) { func (b *Bloom) Add(d []byte) {
b.add(d, make([]byte, 6)) var buf [6]byte
b.AddWithBuffer(d, &buf)
} }
// add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes) // add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes)
func (b *Bloom) add(d []byte, buf []byte) { func (b *Bloom) AddWithBuffer(d []byte, buf *[6]byte) {
i1, v1, i2, v2, i3, v3 := bloomValues(d, buf) i1, v1, i2, v2, i3, v3 := bloomValues(d, buf)
b[i1] |= v1 b[i1] |= v1
b[i2] |= v2 b[i2] |= v2
@ -84,7 +85,8 @@ func (b Bloom) Bytes() []byte {
// Test checks if the given topic is present in the bloom filter // Test checks if the given topic is present in the bloom filter
func (b Bloom) Test(topic []byte) bool { func (b Bloom) Test(topic []byte) bool {
i1, v1, i2, v2, i3, v3 := bloomValues(topic, make([]byte, 6)) var buf [6]byte
i1, v1, i2, v2, i3, v3 := bloomValues(topic, &buf)
return v1 == v1&b[i1] && return v1 == v1&b[i1] &&
v2 == v2&b[i2] && v2 == v2&b[i2] &&
v3 == v3&b[i3] v3 == v3&b[i3]
@ -104,12 +106,12 @@ func (b *Bloom) UnmarshalText(input []byte) error {
func CreateBloom(receipt *Receipt) Bloom { func CreateBloom(receipt *Receipt) Bloom {
var ( var (
bin Bloom bin Bloom
buf = make([]byte, 6) buf [6]byte
) )
for _, log := range receipt.Logs { for _, log := range receipt.Logs {
bin.add(log.Address.Bytes(), buf) bin.AddWithBuffer(log.Address.Bytes(), &buf)
for _, b := range log.Topics { for _, b := range log.Topics {
bin.add(b[:], buf) bin.AddWithBuffer(b[:], &buf)
} }
} }
return bin return bin
@ -139,21 +141,20 @@ func Bloom9(data []byte) []byte {
} }
// bloomValues returns the bytes (index-value pairs) to set for the given data // bloomValues returns the bytes (index-value pairs) to set for the given data
func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byte) { func bloomValues(data []byte, hashbuf *[6]byte) (uint, byte, uint, byte, uint, byte) {
sha := hasherPool.Get().(crypto.KeccakState) sha := hasherPool.Get().(crypto.KeccakState)
sha.Reset() sha.Reset()
sha.Write(data) sha.Write(data)
sha.Read(hashbuf) sha.Read(hashbuf[:])
hasherPool.Put(sha) hasherPool.Put(sha)
// The actual bits to flip // The actual bits to flip
v1 := byte(1 << (hashbuf[1] & 0x7)) v1 := byte(1 << (hashbuf[1] & 0x7))
v2 := byte(1 << (hashbuf[3] & 0x7)) v2 := byte(1 << (hashbuf[3] & 0x7))
v3 := byte(1 << (hashbuf[5] & 0x7)) v3 := byte(1 << (hashbuf[5] & 0x7))
// The indices for the bytes to OR in // The indices for the bytes to OR in
i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf)&0x7ff)>>3) - 1 i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[0:])&0x7ff)>>3) - 1
i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1 i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1
i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1 i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1
return i1, v1, i2, v2, i3, v3 return i1, v1, i2, v2, i3, v3
} }

View file

@ -17,11 +17,10 @@ var _ = (*accountMarshaling)(nil)
// MarshalJSON marshals as JSON. // MarshalJSON marshals as JSON.
func (a Account) MarshalJSON() ([]byte, error) { func (a Account) MarshalJSON() ([]byte, error) {
type Account struct { type Account struct {
Code hexutil.Bytes `json:"code,omitempty"` Code hexutil.Bytes `json:"code,omitempty"`
Storage map[storageJSON]storageJSON `json:"storage,omitempty"` Storage map[storageJSON]storageJSON `json:"storage,omitempty"`
Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"` Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"`
Nonce math.HexOrDecimal64 `json:"nonce,omitempty"` Nonce math.HexOrDecimal64 `json:"nonce,omitempty"`
PrivateKey hexutil.Bytes `json:"secretKey,omitempty"`
} }
var enc Account var enc Account
enc.Code = a.Code enc.Code = a.Code
@ -33,18 +32,16 @@ func (a Account) MarshalJSON() ([]byte, error) {
} }
enc.Balance = (*math.HexOrDecimal256)(a.Balance) enc.Balance = (*math.HexOrDecimal256)(a.Balance)
enc.Nonce = math.HexOrDecimal64(a.Nonce) enc.Nonce = math.HexOrDecimal64(a.Nonce)
enc.PrivateKey = a.PrivateKey
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON. // UnmarshalJSON unmarshals from JSON.
func (a *Account) UnmarshalJSON(input []byte) error { func (a *Account) UnmarshalJSON(input []byte) error {
type Account struct { type Account struct {
Code *hexutil.Bytes `json:"code,omitempty"` Code *hexutil.Bytes `json:"code,omitempty"`
Storage map[storageJSON]storageJSON `json:"storage,omitempty"` Storage map[storageJSON]storageJSON `json:"storage,omitempty"`
Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"` Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"`
Nonce *math.HexOrDecimal64 `json:"nonce,omitempty"` Nonce *math.HexOrDecimal64 `json:"nonce,omitempty"`
PrivateKey *hexutil.Bytes `json:"secretKey,omitempty"`
} }
var dec Account var dec Account
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -66,8 +63,5 @@ func (a *Account) UnmarshalJSON(input []byte) error {
if dec.Nonce != nil { if dec.Nonce != nil {
a.Nonce = uint64(*dec.Nonce) a.Nonce = uint64(*dec.Nonce)
} }
if dec.PrivateKey != nil {
a.PrivateKey = *dec.PrivateKey
}
return nil return nil
} }

View file

@ -25,12 +25,11 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/sha3"
) )
// hasherPool holds LegacyKeccak256 hashers for rlpHash. // hasherPool holds LegacyKeccak256 hashers for rlpHash.
var hasherPool = sync.Pool{ var hasherPool = sync.Pool{
New: func() interface{} { return sha3.NewLegacyKeccak256() }, New: func() interface{} { return crypto.NewKeccakState() },
} }
// encodeBufferPool holds temporary encoder buffers for DeriveSha and TX encoding. // encodeBufferPool holds temporary encoder buffers for DeriveSha and TX encoding.

View file

@ -27,6 +27,7 @@ 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/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -258,7 +259,7 @@ func (r *Receipt) Size() common.StorageSize {
} }
// ReceiptForStorage is a wrapper around a Receipt with RLP serialization // ReceiptForStorage is a wrapper around a Receipt with RLP serialization
// that omits the Bloom field and deserialization that re-computes it. // that omits the Bloom field. The Bloom field is recomputed by DeriveFields.
type ReceiptForStorage Receipt type ReceiptForStorage Receipt
// EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt // EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt
@ -291,7 +292,6 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error {
} }
r.CumulativeGasUsed = stored.CumulativeGasUsed r.CumulativeGasUsed = stored.CumulativeGasUsed
r.Logs = stored.Logs r.Logs = stored.Logs
r.Bloom = CreateBloom((*Receipt)(r))
return nil return nil
} }
@ -372,6 +372,26 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu
rs[i].Logs[j].Index = logIndex rs[i].Logs[j].Index = logIndex
logIndex++ logIndex++
} }
// also derive the Bloom if not derived yet
rs[i].Bloom = CreateBloom(rs[i])
} }
return nil return nil
} }
// EncodeBlockReceiptLists encodes a list of block receipt lists into RLP.
func EncodeBlockReceiptLists(receipts []Receipts) []rlp.RawValue {
var storageReceipts []*ReceiptForStorage
result := make([]rlp.RawValue, len(receipts))
for i, receipt := range receipts {
storageReceipts = storageReceipts[:0]
for _, r := range receipt {
storageReceipts = append(storageReceipts, (*ReceiptForStorage)(r))
}
bytes, err := rlp.EncodeToBytes(storageReceipts)
if err != nil {
log.Crit("Failed to encode block receipts", "err", err)
}
result[i] = bytes
}
return result
}

View file

@ -22,6 +22,7 @@ import (
"math" "math"
"math/big" "math/big"
"reflect" "reflect"
"sync"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -154,148 +155,161 @@ var (
blockNumber = big.NewInt(1) blockNumber = big.NewInt(1)
blockTime = uint64(2) blockTime = uint64(2)
blockHash = common.BytesToHash([]byte{0x03, 0x14}) blockHash = common.BytesToHash([]byte{0x03, 0x14})
// Create the corresponding receipts
receipts = Receipts{
&Receipt{
Status: ReceiptStatusFailed,
CumulativeGasUsed: 1,
Logs: []*Log{
{
Address: common.BytesToAddress([]byte{0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[0].Hash(),
TxIndex: 0,
BlockHash: blockHash,
Index: 0,
},
{
Address: common.BytesToAddress([]byte{0x01, 0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[0].Hash(),
TxIndex: 0,
BlockHash: blockHash,
Index: 1,
},
},
// derived fields:
TxHash: txs[0].Hash(),
ContractAddress: common.HexToAddress("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
GasUsed: 1,
EffectiveGasPrice: big.NewInt(11),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 0,
},
&Receipt{
PostState: common.Hash{2}.Bytes(),
CumulativeGasUsed: 3,
Logs: []*Log{
{
Address: common.BytesToAddress([]byte{0x22}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[1].Hash(),
TxIndex: 1,
BlockHash: blockHash,
Index: 2,
},
{
Address: common.BytesToAddress([]byte{0x02, 0x22}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[1].Hash(),
TxIndex: 1,
BlockHash: blockHash,
Index: 3,
},
},
// derived fields:
TxHash: txs[1].Hash(),
GasUsed: 2,
EffectiveGasPrice: big.NewInt(22),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 1,
},
&Receipt{
Type: AccessListTxType,
PostState: common.Hash{3}.Bytes(),
CumulativeGasUsed: 6,
Logs: []*Log{},
// derived fields:
TxHash: txs[2].Hash(),
GasUsed: 3,
EffectiveGasPrice: big.NewInt(33),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 2,
},
&Receipt{
Type: DynamicFeeTxType,
PostState: common.Hash{4}.Bytes(),
CumulativeGasUsed: 10,
Logs: []*Log{},
// derived fields:
TxHash: txs[3].Hash(),
GasUsed: 4,
EffectiveGasPrice: big.NewInt(1044),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 3,
},
&Receipt{
Type: DynamicFeeTxType,
PostState: common.Hash{5}.Bytes(),
CumulativeGasUsed: 15,
Logs: []*Log{},
// derived fields:
TxHash: txs[4].Hash(),
GasUsed: 5,
EffectiveGasPrice: big.NewInt(1055),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 4,
},
&Receipt{
Type: BlobTxType,
PostState: common.Hash{6}.Bytes(),
CumulativeGasUsed: 21,
Logs: []*Log{},
// derived fields:
TxHash: txs[5].Hash(),
GasUsed: 6,
EffectiveGasPrice: big.NewInt(1066),
BlobGasUsed: params.BlobTxBlobGasPerBlob,
BlobGasPrice: big.NewInt(920),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 5,
},
&Receipt{
Type: BlobTxType,
PostState: common.Hash{7}.Bytes(),
CumulativeGasUsed: 28,
Logs: []*Log{},
// derived fields:
TxHash: txs[6].Hash(),
GasUsed: 7,
EffectiveGasPrice: big.NewInt(1077),
BlobGasUsed: 3 * params.BlobTxBlobGasPerBlob,
BlobGasPrice: big.NewInt(920),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 6,
},
}
) )
var receiptsOnce sync.Once
var testReceipts Receipts
func getTestReceipts() Receipts {
// Compute the blooms only once
receiptsOnce.Do(func() {
// Create the corresponding receipts
r := Receipts{
&Receipt{
Status: ReceiptStatusFailed,
CumulativeGasUsed: 1,
Logs: []*Log{
{
Address: common.BytesToAddress([]byte{0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[0].Hash(),
TxIndex: 0,
BlockHash: blockHash,
Index: 0,
},
{
Address: common.BytesToAddress([]byte{0x01, 0x11}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[0].Hash(),
TxIndex: 0,
BlockHash: blockHash,
Index: 1,
},
},
// derived fields:
TxHash: txs[0].Hash(),
ContractAddress: common.HexToAddress("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"),
GasUsed: 1,
EffectiveGasPrice: big.NewInt(11),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 0,
},
&Receipt{
PostState: common.Hash{2}.Bytes(),
CumulativeGasUsed: 3,
Logs: []*Log{
{
Address: common.BytesToAddress([]byte{0x22}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[1].Hash(),
TxIndex: 1,
BlockHash: blockHash,
Index: 2,
},
{
Address: common.BytesToAddress([]byte{0x02, 0x22}),
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[1].Hash(),
TxIndex: 1,
BlockHash: blockHash,
Index: 3,
},
},
// derived fields:
TxHash: txs[1].Hash(),
GasUsed: 2,
EffectiveGasPrice: big.NewInt(22),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 1,
},
&Receipt{
Type: AccessListTxType,
PostState: common.Hash{3}.Bytes(),
CumulativeGasUsed: 6,
Logs: []*Log{},
// derived fields:
TxHash: txs[2].Hash(),
GasUsed: 3,
EffectiveGasPrice: big.NewInt(33),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 2,
},
&Receipt{
Type: DynamicFeeTxType,
PostState: common.Hash{4}.Bytes(),
CumulativeGasUsed: 10,
Logs: []*Log{},
// derived fields:
TxHash: txs[3].Hash(),
GasUsed: 4,
EffectiveGasPrice: big.NewInt(1044),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 3,
},
&Receipt{
Type: DynamicFeeTxType,
PostState: common.Hash{5}.Bytes(),
CumulativeGasUsed: 15,
Logs: []*Log{},
// derived fields:
TxHash: txs[4].Hash(),
GasUsed: 5,
EffectiveGasPrice: big.NewInt(1055),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 4,
},
&Receipt{
Type: BlobTxType,
PostState: common.Hash{6}.Bytes(),
CumulativeGasUsed: 21,
Logs: []*Log{},
// derived fields:
TxHash: txs[5].Hash(),
GasUsed: 6,
EffectiveGasPrice: big.NewInt(1066),
BlobGasUsed: params.BlobTxBlobGasPerBlob,
BlobGasPrice: big.NewInt(920),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 5,
},
&Receipt{
Type: BlobTxType,
PostState: common.Hash{7}.Bytes(),
CumulativeGasUsed: 28,
Logs: []*Log{},
// derived fields:
TxHash: txs[6].Hash(),
GasUsed: 7,
EffectiveGasPrice: big.NewInt(1077),
BlobGasUsed: 3 * params.BlobTxBlobGasPerBlob,
BlobGasPrice: big.NewInt(920),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 6,
},
}
for _, receipt := range r {
receipt.Bloom = CreateBloom(receipt)
}
testReceipts = r
})
return testReceipts
}
func TestDecodeEmptyTypedReceipt(t *testing.T) { func TestDecodeEmptyTypedReceipt(t *testing.T) {
input := []byte{0x80} input := []byte{0x80}
var r Receipt var r Receipt
@ -310,6 +324,7 @@ func TestDeriveFields(t *testing.T) {
// Re-derive receipts. // Re-derive receipts.
basefee := big.NewInt(1000) basefee := big.NewInt(1000)
blobGasPrice := big.NewInt(920) blobGasPrice := big.NewInt(920)
receipts := getTestReceipts()
derivedReceipts := clearComputedFieldsOnReceipts(receipts) derivedReceipts := clearComputedFieldsOnReceipts(receipts)
err := Receipts(derivedReceipts).DeriveFields(params.TestChainConfig, blockHash, blockNumber.Uint64(), blockTime, basefee, blobGasPrice, txs) err := Receipts(derivedReceipts).DeriveFields(params.TestChainConfig, blockHash, blockNumber.Uint64(), blockTime, basefee, blobGasPrice, txs)
if err != nil { if err != nil {
@ -335,6 +350,7 @@ func TestDeriveFields(t *testing.T) {
// Test that we can marshal/unmarshal receipts to/from json without errors. // Test that we can marshal/unmarshal receipts to/from json without errors.
// This also confirms that our test receipts contain all the required fields. // This also confirms that our test receipts contain all the required fields.
func TestReceiptJSON(t *testing.T) { func TestReceiptJSON(t *testing.T) {
receipts := getTestReceipts()
for i := range receipts { for i := range receipts {
b, err := receipts[i].MarshalJSON() b, err := receipts[i].MarshalJSON()
if err != nil { if err != nil {
@ -351,6 +367,7 @@ func TestReceiptJSON(t *testing.T) {
// Test we can still parse receipt without EffectiveGasPrice for backwards compatibility, even // Test we can still parse receipt without EffectiveGasPrice for backwards compatibility, even
// though it is required per the spec. // though it is required per the spec.
func TestEffectiveGasPriceNotRequired(t *testing.T) { func TestEffectiveGasPriceNotRequired(t *testing.T) {
receipts := getTestReceipts()
r := *receipts[0] r := *receipts[0]
r.EffectiveGasPrice = nil r.EffectiveGasPrice = nil
b, err := r.MarshalJSON() b, err := r.MarshalJSON()
@ -511,6 +528,7 @@ func clearComputedFieldsOnReceipt(receipt *Receipt) *Receipt {
cpy.EffectiveGasPrice = big.NewInt(0) cpy.EffectiveGasPrice = big.NewInt(0)
cpy.BlobGasUsed = 0 cpy.BlobGasUsed = 0
cpy.BlobGasPrice = nil cpy.BlobGasPrice = nil
cpy.Bloom = CreateBloom(&cpy)
return &cpy return &cpy
} }

View file

@ -355,28 +355,31 @@ func (tx *Transaction) GasTipCapIntCmp(other *big.Int) int {
// Note: if the effective gasTipCap is negative, this method returns both error // Note: if the effective gasTipCap is negative, this method returns both error
// the actual negative value, _and_ ErrGasFeeCapTooLow // the actual negative value, _and_ ErrGasFeeCapTooLow
func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) { func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) {
dst := new(big.Int)
err := tx.calcEffectiveGasTip(dst, baseFee)
return dst, err
}
// calcEffectiveGasTip calculates the effective gas tip of the transaction and
// saves the result to dst.
func (tx *Transaction) calcEffectiveGasTip(dst *big.Int, baseFee *big.Int) error {
if baseFee == nil { if baseFee == nil {
return tx.GasTipCap(), nil dst.Set(tx.inner.gasTipCap())
return nil
} }
var err error var err error
gasFeeCap := tx.GasFeeCap() gasFeeCap := tx.inner.gasFeeCap()
if gasFeeCap.Cmp(baseFee) < 0 { if gasFeeCap.Cmp(baseFee) < 0 {
err = ErrGasFeeCapTooLow err = ErrGasFeeCapTooLow
} }
gasFeeCap = gasFeeCap.Sub(gasFeeCap, baseFee)
gasTipCap := tx.GasTipCap() dst.Sub(gasFeeCap, baseFee)
if gasTipCap.Cmp(gasFeeCap) < 0 { gasTipCap := tx.inner.gasTipCap()
return gasTipCap, err if gasTipCap.Cmp(dst) < 0 {
dst.Set(gasTipCap)
} }
return gasFeeCap, err return err
}
// EffectiveGasTipValue is identical to EffectiveGasTip, but does not return an
// error in case the effective gasTipCap is negative
func (tx *Transaction) EffectiveGasTipValue(baseFee *big.Int) *big.Int {
effectiveTip, _ := tx.EffectiveGasTip(baseFee)
return effectiveTip
} }
// EffectiveGasTipCmp compares the effective gasTipCap of two transactions assuming the given base fee. // EffectiveGasTipCmp compares the effective gasTipCap of two transactions assuming the given base fee.
@ -384,7 +387,11 @@ func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *big.Int)
if baseFee == nil { if baseFee == nil {
return tx.GasTipCapCmp(other) return tx.GasTipCapCmp(other)
} }
return tx.EffectiveGasTipValue(baseFee).Cmp(other.EffectiveGasTipValue(baseFee)) // Use more efficient internal method.
txTip, otherTip := new(big.Int), new(big.Int)
tx.calcEffectiveGasTip(txTip, baseFee)
other.calcEffectiveGasTip(otherTip, baseFee)
return txTip.Cmp(otherTip)
} }
// EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap. // EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap.
@ -392,7 +399,9 @@ func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) i
if baseFee == nil { if baseFee == nil {
return tx.GasTipCapIntCmp(other) return tx.GasTipCapIntCmp(other)
} }
return tx.EffectiveGasTipValue(baseFee).Cmp(other) txTip := new(big.Int)
tx.calcEffectiveGasTip(txTip, baseFee)
return txTip.Cmp(other)
} }
// BlobGas returns the blob gas limit of the transaction for blob transactions, 0 otherwise. // BlobGas returns the blob gas limit of the transaction for blob transactions, 0 otherwise.

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -593,3 +594,101 @@ func BenchmarkHash(b *testing.B) {
signer.Hash(tx) signer.Hash(tx)
} }
} }
func BenchmarkEffectiveGasTip(b *testing.B) {
signer := LatestSigner(params.TestChainConfig)
key, _ := crypto.GenerateKey()
txdata := &DynamicFeeTx{
ChainID: big.NewInt(1),
Nonce: 0,
GasTipCap: big.NewInt(2000000000),
GasFeeCap: big.NewInt(3000000000),
Gas: 21000,
To: &common.Address{},
Value: big.NewInt(0),
Data: nil,
}
tx, _ := SignNewTx(key, signer, txdata)
baseFee := big.NewInt(1000000000) // 1 gwei
b.Run("Original", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := tx.EffectiveGasTip(baseFee)
if err != nil {
b.Fatal(err)
}
}
})
b.Run("IntoMethod", func(b *testing.B) {
b.ReportAllocs()
dst := new(big.Int)
for i := 0; i < b.N; i++ {
err := tx.calcEffectiveGasTip(dst, baseFee)
if err != nil {
b.Fatal(err)
}
}
})
}
func TestEffectiveGasTipInto(t *testing.T) {
signer := LatestSigner(params.TestChainConfig)
key, _ := crypto.GenerateKey()
testCases := []struct {
tipCap int64
feeCap int64
baseFee *int64
}{
{tipCap: 1, feeCap: 100, baseFee: intPtr(50)},
{tipCap: 10, feeCap: 100, baseFee: intPtr(50)},
{tipCap: 50, feeCap: 100, baseFee: intPtr(50)},
{tipCap: 100, feeCap: 100, baseFee: intPtr(50)},
{tipCap: 1, feeCap: 50, baseFee: intPtr(50)},
{tipCap: 1, feeCap: 20, baseFee: intPtr(50)}, // Base fee higher than fee cap
{tipCap: 50, feeCap: 100, baseFee: intPtr(0)},
{tipCap: 50, feeCap: 100, baseFee: nil}, // nil base fee
}
for i, tc := range testCases {
txdata := &DynamicFeeTx{
ChainID: big.NewInt(1),
Nonce: 0,
GasTipCap: big.NewInt(tc.tipCap),
GasFeeCap: big.NewInt(tc.feeCap),
Gas: 21000,
To: &common.Address{},
Value: big.NewInt(0),
Data: nil,
}
tx, _ := SignNewTx(key, signer, txdata)
var baseFee *big.Int
if tc.baseFee != nil {
baseFee = big.NewInt(*tc.baseFee)
}
// Get result from original method
orig, origErr := tx.EffectiveGasTip(baseFee)
// Get result from new method
dst := new(big.Int)
newErr := tx.calcEffectiveGasTip(dst, baseFee)
// Compare results
if (origErr != nil) != (newErr != nil) {
t.Fatalf("case %d: error mismatch: orig %v, new %v", i, origErr, newErr)
}
if orig.Cmp(dst) != 0 {
t.Fatalf("case %d: result mismatch: orig %v, new %v", i, orig, dst)
}
}
}
// Helper function to create integer pointer
func intPtr(i int64) *int64 {
return &i
}

View file

@ -139,7 +139,7 @@ var PrecompiledContractsPrague = PrecompiledContracts{
var PrecompiledContractsBLS = PrecompiledContractsPrague var PrecompiledContractsBLS = PrecompiledContractsPrague
var PrecompiledContractsVerkle = PrecompiledContractsPrague var PrecompiledContractsVerkle = PrecompiledContractsBerlin
var ( var (
PrecompiledAddressesPrague []common.Address PrecompiledAddressesPrague []common.Address

View file

@ -370,7 +370,7 @@ func BenchmarkPrecompiledBLS12381G1MultiExpWorstCase(b *testing.B) {
Name: "WorstCaseG1", Name: "WorstCaseG1",
NoBenchmark: false, NoBenchmark: false,
} }
benchmarkPrecompiled("f0c", testcase, b) benchmarkPrecompiled("f0b", testcase, b)
} }
// BenchmarkPrecompiledBLS12381G2MultiExpWorstCase benchmarks the worst case we could find that still fits a gaslimit of 10MGas. // BenchmarkPrecompiledBLS12381G2MultiExpWorstCase benchmarks the worst case we could find that still fits a gaslimit of 10MGas.
@ -391,5 +391,5 @@ func BenchmarkPrecompiledBLS12381G2MultiExpWorstCase(b *testing.B) {
Name: "WorstCaseG2", Name: "WorstCaseG2",
NoBenchmark: false, NoBenchmark: false,
} }
benchmarkPrecompiled("f0f", testcase, b) benchmarkPrecompiled("f0d", testcase, b)
} }

View file

@ -339,12 +339,10 @@ func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeC
addr := common.Address(a.Bytes20()) addr := common.Address(a.Bytes20())
code := interpreter.evm.StateDB.GetCode(addr) code := interpreter.evm.StateDB.GetCode(addr)
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
if !scope.Contract.IsSystemCall { consumed, wanted := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, scope.Contract.Gas)
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false) scope.Contract.UseGas(consumed, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified)
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { if consumed < wanted {
scope.Contract.Gas = 0 return nil, ErrOutOfGas
return nil, ErrOutOfGas
}
} }
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy)
@ -367,9 +365,9 @@ func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
// touch next chunk if PUSH1 is at the boundary. if so, *pc has // touch next chunk if PUSH1 is at the boundary. if so, *pc has
// advanced past this boundary. // advanced past this boundary.
contractAddr := scope.Contract.Address() contractAddr := scope.Contract.Address()
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) consumed, wanted := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas)
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.UseGas(wanted, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified)
scope.Contract.Gas = 0 if consumed < wanted {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
} }
@ -395,9 +393,9 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc {
if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall { if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall {
contractAddr := scope.Contract.Address() contractAddr := scope.Contract.Address()
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) consumed, wanted := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas)
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.UseGas(consumed, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified)
scope.Contract.Gas = 0 if consumed < wanted {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
} }

View file

@ -206,8 +206,14 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
if !evm.StateDB.Exist(addr) { if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) { if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) {
// add proof of absence to witness // Add proof of absence to witness
wgas := evm.AccessEvents.AddAccount(addr, false) // At this point, the read costs have already been charged, either because this
// is a direct tx call, in which case it's covered by the intrinsic gas, or because
// of a CALL instruction, in which case BASIC_DATA has been added to the access
// list in write mode. If there is enough gas paying for the addition of the code
// hash leaf to the access list, then account creation will proceed unimpaired.
// Thus, only pay for the creation of the code hash leaf here.
wgas := evm.AccessEvents.CodeHashGas(addr, true, gas, false)
if gas < wgas { if gas < wgas {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
return nil, 0, ErrOutOfGas return nil, 0, ErrOutOfGas
@ -433,7 +439,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
// Charge the contract creation init gas in verkle mode // Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 { if evm.chainRules.IsEIP4762 {
statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address) statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas)
if statelessGas > gas { if statelessGas > gas {
return nil, common.Address{}, 0, ErrOutOfGas return nil, common.Address{}, 0, ErrOutOfGas
} }
@ -481,14 +487,14 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
} }
// Charge the contract creation init gas in verkle mode // Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 { if evm.chainRules.IsEIP4762 {
statelessGas := evm.AccessEvents.ContractCreateInitGas(address) consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas)
if statelessGas > gas { if consumed < wanted {
return nil, common.Address{}, 0, ErrOutOfGas return nil, common.Address{}, 0, ErrOutOfGas
} }
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractInit) evm.Config.Tracer.OnGasChange(gas, gas-consumed, tracing.GasChangeWitnessContractInit)
} }
gas = gas - statelessGas gas = gas - consumed
} }
evm.Context.Transfer(evm.StateDB, caller, address, value) evm.Context.Transfer(evm.StateDB, caller, address, value)
@ -535,7 +541,9 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
return ret, ErrCodeStoreOutOfGas return ret, ErrCodeStoreOutOfGas
} }
} else { } else {
if len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas)
contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
if len(ret) > 0 && (consumed < wanted) {
return ret, ErrCodeStoreOutOfGas return ret, ErrCodeStoreOutOfGas
} }
} }
@ -555,7 +563,8 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *ui
// The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:] // The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:]
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
func (evm *EVM) Create2(caller common.Address, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { func (evm *EVM) Create2(caller common.Address, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), crypto.Keccak256(code)) inithash := crypto.HashData(evm.interpreter.hasher, code)
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:])
return evm.create(caller, code, gas, endowment, contractAddr, CREATE2) return evm.create(caller, code, gas, endowment, contractAddr, CREATE2)
} }

View file

@ -394,14 +394,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if evm.chainRules.IsEIP4762 && !contract.IsSystemCall {
if transfersValue {
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
if overflow {
return 0, ErrGasUintOverflow
}
}
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
if err != nil { if err != nil {
return 0, err return 0, err
@ -428,16 +421,6 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if evm.chainRules.IsEIP4762 && !contract.IsSystemCall {
address := common.Address(stack.Back(1).Bytes20())
transfersValue := !stack.Back(2).IsZero()
if transfersValue {
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
if overflow {
return 0, ErrGasUintOverflow
}
}
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
if err != nil { if err != nil {
return 0, err return 0, err

View file

@ -22,7 +22,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"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/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -234,11 +233,7 @@ func opKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
offset, size := scope.Stack.pop(), scope.Stack.peek() offset, size := scope.Stack.pop(), scope.Stack.peek()
data := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) data := scope.Memory.GetPtr(offset.Uint64(), size.Uint64())
if interpreter.hasher == nil { interpreter.hasher.Reset()
interpreter.hasher = crypto.NewKeccakState()
} else {
interpreter.hasher.Reset()
}
interpreter.hasher.Write(data) interpreter.hasher.Write(data)
interpreter.hasher.Read(interpreter.hasherBuf[:]) interpreter.hasher.Read(interpreter.hasherBuf[:])

View file

@ -69,7 +69,7 @@ type StateDB interface {
SelfDestruct6780(common.Address) (uint256.Int, bool) SelfDestruct6780(common.Address) (uint256.Int, bool)
// Exist reports whether the given account exists in state. // Exist reports whether the given account exists in state.
// Notably this should also return true for self-destructed accounts. // Notably this also returns true for self-destructed accounts within the current transaction.
Exist(common.Address) bool Exist(common.Address) bool
// Empty returns whether the given account is empty. Empty // Empty returns whether the given account is empty. Empty
// is defined according to EIP161 (balance = nonce = code = 0). // is defined according to EIP161 (balance = nonce = code = 0).

View file

@ -150,7 +150,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
} }
} }
evm.Config.ExtraEips = extraEips evm.Config.ExtraEips = extraEips
return &EVMInterpreter{evm: evm, table: table} return &EVMInterpreter{evm: evm, table: table, hasher: crypto.NewKeccakState()}
} }
// Run loops and evaluates the contract's code with the given input data and returns // Run loops and evaluates the contract's code with the given input data and returns
@ -237,7 +237,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// if the PC ends up in a new "chunk" of verkleized code, charge the // if the PC ends up in a new "chunk" of verkleized code, charge the
// associated costs. // associated costs.
contractAddr := contract.Address() contractAddr := contract.Address()
contract.Gas -= in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false) consumed, wanted := in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas)
contract.UseGas(consumed, in.evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
if consumed < wanted {
return nil, ErrOutOfGas
}
} }
// Get the operation from the jump table and validate the stack to ensure there are // Get the operation from the jump table and validate the stack to ensure there are

View file

@ -87,7 +87,7 @@ func validate(jt JumpTable) JumpTable {
} }
func newVerkleInstructionSet() JumpTable { func newVerkleInstructionSet() JumpTable {
instructionSet := newCancunInstructionSet() instructionSet := newShanghaiInstructionSet()
enable4762(&instructionSet) enable4762(&instructionSet)
return validate(instructionSet) return validate(instructionSet)
} }

View file

@ -25,31 +25,16 @@ import (
) )
func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true) return evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true, contract.Gas, true), nil
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false) return evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false, contract.Gas, true), nil
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
if contract.IsSystemCall {
return 0, nil
}
address := stack.peek().Bytes20() address := stack.peek().Bytes20()
gas := evm.AccessEvents.BasicDataGas(address, false) return evm.AccessEvents.BasicDataGas(address, false, contract.Gas, true), nil
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
@ -57,56 +42,69 @@ func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
if _, isPrecompile := evm.precompile(address); isPrecompile { if _, isPrecompile := evm.precompile(address); isPrecompile {
return 0, nil return 0, nil
} }
if contract.IsSystemCall { return evm.AccessEvents.BasicDataGas(address, false, contract.Gas, true), nil
return 0, nil
}
gas := evm.AccessEvents.BasicDataGas(address, false)
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
if contract.IsSystemCall {
return 0, nil
}
address := stack.peek().Bytes20() address := stack.peek().Bytes20()
if _, isPrecompile := evm.precompile(address); isPrecompile { if _, isPrecompile := evm.precompile(address); isPrecompile {
return 0, nil return 0, nil
} }
gas := evm.AccessEvents.CodeHashGas(address, false) return evm.AccessEvents.CodeHashGas(address, false, contract.Gas, true), nil
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
return gas, nil
} }
func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := oldCalculator(evm, contract, stack, mem, memorySize) var (
if err != nil { target = common.Address(stack.Back(1).Bytes20())
return 0, err witnessGas uint64
} _, isPrecompile = evm.precompile(target)
if contract.IsSystemCall { isSystemContract = target == params.HistoryStorageAddress
return gas, nil )
}
if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile { // If value is transferred, it is charged before 1/64th
return gas, nil // is subtracted from the available gas pool.
} if withTransferCosts && !stack.Back(2).IsZero() {
witnessGas := evm.AccessEvents.MessageCallGas(contract.Address()) wantedValueTransferWitnessGas := evm.AccessEvents.ValueTransferGas(contract.Address(), target, contract.Gas)
if witnessGas == 0 { if wantedValueTransferWitnessGas > contract.Gas {
return wantedValueTransferWitnessGas, nil
}
witnessGas = wantedValueTransferWitnessGas
} else if isPrecompile || isSystemContract {
witnessGas = params.WarmStorageReadCostEIP2929 witnessGas = params.WarmStorageReadCostEIP2929
} else {
// The charging for the value transfer is done BEFORE subtracting
// the 1/64th gas, as this is considered part of the CALL instruction.
// (so before we get to this point)
// But the message call is part of the subcall, for which only 63/64th
// of the gas should be available.
wantedMessageCallWitnessGas := evm.AccessEvents.MessageCallGas(target, contract.Gas-witnessGas)
var overflow bool
if witnessGas, overflow = math.SafeAdd(witnessGas, wantedMessageCallWitnessGas); overflow {
return 0, ErrGasUintOverflow
}
if witnessGas > contract.Gas {
return witnessGas, nil
}
} }
return witnessGas + gas, nil
contract.Gas -= witnessGas
// if the operation fails, adds witness gas to the gas before returning the error
gas, err := oldCalculator(evm, contract, stack, mem, memorySize)
contract.Gas += witnessGas // restore witness gas so that it can be charged at the callsite
var overflow bool
if gas, overflow = math.SafeAdd(gas, witnessGas); overflow {
return 0, ErrGasUintOverflow
}
return gas, err
} }
} }
var ( var (
gasCallEIP4762 = makeCallVariantGasEIP4762(gasCall) gasCallEIP4762 = makeCallVariantGasEIP4762(gasCall, true)
gasCallCodeEIP4762 = makeCallVariantGasEIP4762(gasCallCode) gasCallCodeEIP4762 = makeCallVariantGasEIP4762(gasCallCode, false)
gasStaticCallEIP4762 = makeCallVariantGasEIP4762(gasStaticCall) gasStaticCallEIP4762 = makeCallVariantGasEIP4762(gasStaticCall, false)
gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall) gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall, false)
) )
func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
@ -118,15 +116,44 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem
return 0, nil return 0, nil
} }
contractAddr := contract.Address() contractAddr := contract.Address()
statelessGas := evm.AccessEvents.BasicDataGas(contractAddr, false) wanted := evm.AccessEvents.BasicDataGas(contractAddr, false, contract.Gas, false)
if wanted > contract.Gas {
return wanted, nil
}
statelessGas := wanted
balanceIsZero := evm.StateDB.GetBalance(contractAddr).Sign() == 0
_, isPrecompile := evm.precompile(beneficiaryAddr)
isSystemContract := beneficiaryAddr == params.HistoryStorageAddress
if (isPrecompile || isSystemContract) && balanceIsZero {
return statelessGas, nil
}
if contractAddr != beneficiaryAddr { if contractAddr != beneficiaryAddr {
statelessGas += evm.AccessEvents.BasicDataGas(beneficiaryAddr, false) wanted := evm.AccessEvents.BasicDataGas(beneficiaryAddr, false, contract.Gas-statelessGas, false)
if wanted > contract.Gas-statelessGas {
return statelessGas + wanted, nil
}
statelessGas += wanted
} }
// Charge write costs if it transfers value // Charge write costs if it transfers value
if evm.StateDB.GetBalance(contractAddr).Sign() != 0 { if !balanceIsZero {
statelessGas += evm.AccessEvents.BasicDataGas(contractAddr, true) wanted := evm.AccessEvents.BasicDataGas(contractAddr, true, contract.Gas-statelessGas, false)
if wanted > contract.Gas-statelessGas {
return statelessGas + wanted, nil
}
statelessGas += wanted
if contractAddr != beneficiaryAddr { if contractAddr != beneficiaryAddr {
statelessGas += evm.AccessEvents.BasicDataGas(beneficiaryAddr, true) if evm.StateDB.Exist(beneficiaryAddr) {
wanted = evm.AccessEvents.BasicDataGas(beneficiaryAddr, true, contract.Gas-statelessGas, false)
} else {
wanted = evm.AccessEvents.AddAccount(beneficiaryAddr, true, contract.Gas-statelessGas)
}
if wanted > contract.Gas-statelessGas {
return statelessGas + wanted, nil
}
statelessGas += wanted
} }
} }
return statelessGas, nil return statelessGas, nil
@ -137,17 +164,19 @@ func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
if err != nil { if err != nil {
return 0, err return 0, err
} }
var (
codeOffset = stack.Back(1)
length = stack.Back(2)
)
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
if overflow {
uint64CodeOffset = gomath.MaxUint64
}
_, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64())
if !contract.IsDeployment && !contract.IsSystemCall { if !contract.IsDeployment && !contract.IsSystemCall {
gas += evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) var (
codeOffset = stack.Back(1)
length = stack.Back(2)
)
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
if overflow {
uint64CodeOffset = gomath.MaxUint64
}
_, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64())
_, wanted := evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false, contract.Gas-gas)
gas += wanted
} }
return gas, nil return gas, nil
} }
@ -158,16 +187,17 @@ func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memo
if err != nil { if err != nil {
return 0, err return 0, err
} }
if contract.IsSystemCall { addr := common.Address(stack.peek().Bytes20())
_, isPrecompile := evm.precompile(addr)
if isPrecompile || addr == params.HistoryStorageAddress {
var overflow bool
if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil return gas, nil
} }
addr := common.Address(stack.peek().Bytes20()) wgas := evm.AccessEvents.BasicDataGas(addr, false, contract.Gas-gas, true)
wgas := evm.AccessEvents.BasicDataGas(addr, false)
if wgas == 0 {
wgas = params.WarmStorageReadCostEIP2929
}
var overflow bool var overflow bool
// We charge (cold-warm), since 'warm' is already charged as constantGas
if gas, overflow = math.SafeAdd(gas, wgas); overflow { if gas, overflow = math.SafeAdd(gas, wgas); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }

View file

@ -28,6 +28,7 @@ import (
"io" "io"
"math/big" "math/big"
"os" "os"
"sync"
"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"
@ -73,6 +74,12 @@ func NewKeccakState() KeccakState {
return sha3.NewLegacyKeccak256().(KeccakState) return sha3.NewLegacyKeccak256().(KeccakState)
} }
var hasherPool = sync.Pool{
New: func() any {
return sha3.NewLegacyKeccak256().(KeccakState)
},
}
// HashData hashes the provided data using the KeccakState and returns a 32 byte hash // HashData hashes the provided data using the KeccakState and returns a 32 byte hash
func HashData(kh KeccakState, data []byte) (h common.Hash) { func HashData(kh KeccakState, data []byte) (h common.Hash) {
kh.Reset() kh.Reset()
@ -84,22 +91,26 @@ func HashData(kh KeccakState, data []byte) (h common.Hash) {
// Keccak256 calculates and returns the Keccak256 hash of the input data. // Keccak256 calculates and returns the Keccak256 hash of the input data.
func Keccak256(data ...[]byte) []byte { func Keccak256(data ...[]byte) []byte {
b := make([]byte, 32) b := make([]byte, 32)
d := NewKeccakState() d := hasherPool.Get().(KeccakState)
d.Reset()
for _, b := range data { for _, b := range data {
d.Write(b) d.Write(b)
} }
d.Read(b) d.Read(b)
hasherPool.Put(d)
return b return b
} }
// Keccak256Hash calculates and returns the Keccak256 hash of the input data, // Keccak256Hash calculates and returns the Keccak256 hash of the input data,
// converting it to an internal Hash data structure. // converting it to an internal Hash data structure.
func Keccak256Hash(data ...[]byte) (h common.Hash) { func Keccak256Hash(data ...[]byte) (h common.Hash) {
d := NewKeccakState() d := hasherPool.Get().(KeccakState)
d.Reset()
for _, b := range data { for _, b := range data {
d.Write(b) d.Write(b)
} }
d.Read(h[:]) d.Read(h[:])
hasherPool.Put(d)
return h return h
} }

View file

@ -21,7 +21,7 @@ import (
"testing" "testing"
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
gokzg4844 "github.com/crate-crypto/go-kzg-4844" gokzg4844 "github.com/crate-crypto/go-eth-kzg"
) )
func randFieldElement() [32]byte { func randFieldElement() [32]byte {

View file

@ -14,8 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License // 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/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build !nacl && !js && !wasip1 && cgo && !gofuzz //go:build !nacl && !js && !wasip1 && cgo && !gofuzz && !tinygo
// +build !nacl,!js,!wasip1,cgo,!gofuzz // +build !nacl,!js,!wasip1,cgo,!gofuzz,!tinygo
package crypto package crypto

View file

@ -14,8 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License // 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/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build nacl || js || wasip1 || !cgo || gofuzz //go:build nacl || js || wasip1 || !cgo || gofuzz || tinygo
// +build nacl js wasip1 !cgo gofuzz // +build nacl js wasip1 !cgo gofuzz tinygo
package crypto package crypto

View file

@ -196,7 +196,9 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r
if hash, ok := blockNrOrHash.Hash(); ok { if hash, ok := blockNrOrHash.Hash(); ok {
header := b.eth.blockchain.GetHeaderByHash(hash) header := b.eth.blockchain.GetHeaderByHash(hash)
if header == nil { if header == nil {
return nil, errors.New("header for hash not found") // Return 'null' and no error if block is not found.
// This behavior is required by RPC spec.
return nil, nil
} }
if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash { if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
return nil, errors.New("hash is not currently canonical") return nil, errors.New("hash is not currently canonical")

View file

@ -134,13 +134,17 @@ func TestSendTx(t *testing.T) {
func testSendTx(t *testing.T, withLocal bool) { func testSendTx(t *testing.T, withLocal bool) {
b := initBackend(withLocal) b := initBackend(withLocal)
txA := pricedSetCodeTx(0, 250000, uint256.NewInt(params.GWei), uint256.NewInt(params.GWei), key, []unsignedAuth{ txA := pricedSetCodeTx(0, 250000, uint256.NewInt(params.GWei), uint256.NewInt(params.GWei), key, []unsignedAuth{{nonce: 0, key: key}})
{ if err := b.SendTx(context.Background(), txA); err != nil {
nonce: 0, t.Fatalf("Failed to submit tx: %v", err)
key: key, }
}, for {
}) pending, _ := b.TxPool().ContentFrom(address)
b.SendTx(context.Background(), txA) if len(pending) == 1 {
break
}
time.Sleep(100 * time.Millisecond)
}
txB := makeTx(1, nil, nil, key) txB := makeTx(1, nil, nil, key)
err := b.SendTx(context.Background(), txB) err := b.SendTx(context.Background(), txB)

View file

@ -271,26 +271,26 @@ func storageRangeAt(statedb *state.StateDB, root common.Hash, address common.Add
// //
// With one parameter, returns the list of accounts modified in the specified block. // With one parameter, returns the list of accounts modified in the specified block.
func (api *DebugAPI) GetModifiedAccountsByNumber(startNum uint64, endNum *uint64) ([]common.Address, error) { func (api *DebugAPI) GetModifiedAccountsByNumber(startNum uint64, endNum *uint64) ([]common.Address, error) {
var startBlock, endBlock *types.Block var startHeader, endHeader *types.Header
startBlock = api.eth.blockchain.GetBlockByNumber(startNum) startHeader = api.eth.blockchain.GetHeaderByNumber(startNum)
if startBlock == nil { if startHeader == nil {
return nil, fmt.Errorf("start block %x not found", startNum) return nil, fmt.Errorf("start block %x not found", startNum)
} }
if endNum == nil { if endNum == nil {
endBlock = startBlock endHeader = startHeader
startBlock = api.eth.blockchain.GetBlockByHash(startBlock.ParentHash()) startHeader = api.eth.blockchain.GetHeaderByHash(startHeader.ParentHash)
if startBlock == nil { if startHeader == nil {
return nil, fmt.Errorf("block %x has no parent", endBlock.Number()) return nil, fmt.Errorf("block %x has no parent", endHeader.Number)
} }
} else { } else {
endBlock = api.eth.blockchain.GetBlockByNumber(*endNum) endHeader = api.eth.blockchain.GetHeaderByNumber(*endNum)
if endBlock == nil { if endHeader == nil {
return nil, fmt.Errorf("end block %d not found", *endNum) return nil, fmt.Errorf("end block %d not found", *endNum)
} }
} }
return api.getModifiedAccounts(startBlock, endBlock) return api.getModifiedAccounts(startHeader, endHeader)
} }
// GetModifiedAccountsByHash returns all accounts that have changed between the // GetModifiedAccountsByHash returns all accounts that have changed between the
@ -299,38 +299,38 @@ func (api *DebugAPI) GetModifiedAccountsByNumber(startNum uint64, endNum *uint64
// //
// With one parameter, returns the list of accounts modified in the specified block. // With one parameter, returns the list of accounts modified in the specified block.
func (api *DebugAPI) GetModifiedAccountsByHash(startHash common.Hash, endHash *common.Hash) ([]common.Address, error) { func (api *DebugAPI) GetModifiedAccountsByHash(startHash common.Hash, endHash *common.Hash) ([]common.Address, error) {
var startBlock, endBlock *types.Block var startHeader, endHeader *types.Header
startBlock = api.eth.blockchain.GetBlockByHash(startHash) startHeader = api.eth.blockchain.GetHeaderByHash(startHash)
if startBlock == nil { if startHeader == nil {
return nil, fmt.Errorf("start block %x not found", startHash) return nil, fmt.Errorf("start block %x not found", startHash)
} }
if endHash == nil { if endHash == nil {
endBlock = startBlock endHeader = startHeader
startBlock = api.eth.blockchain.GetBlockByHash(startBlock.ParentHash()) startHeader = api.eth.blockchain.GetHeaderByHash(startHeader.ParentHash)
if startBlock == nil { if startHeader == nil {
return nil, fmt.Errorf("block %x has no parent", endBlock.Number()) return nil, fmt.Errorf("block %x has no parent", endHeader.Number)
} }
} else { } else {
endBlock = api.eth.blockchain.GetBlockByHash(*endHash) endHeader = api.eth.blockchain.GetHeaderByHash(*endHash)
if endBlock == nil { if endHeader == nil {
return nil, fmt.Errorf("end block %x not found", *endHash) return nil, fmt.Errorf("end block %x not found", *endHash)
} }
} }
return api.getModifiedAccounts(startBlock, endBlock) return api.getModifiedAccounts(startHeader, endHeader)
} }
func (api *DebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]common.Address, error) { func (api *DebugAPI) getModifiedAccounts(startHeader, endHeader *types.Header) ([]common.Address, error) {
if startBlock.Number().Uint64() >= endBlock.Number().Uint64() { if startHeader.Number.Uint64() >= endHeader.Number.Uint64() {
return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startBlock.Number().Uint64(), endBlock.Number().Uint64()) return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startHeader.Number.Uint64(), endHeader.Number.Uint64())
} }
triedb := api.eth.BlockChain().TrieDB() triedb := api.eth.BlockChain().TrieDB()
oldTrie, err := trie.NewStateTrie(trie.StateTrieID(startBlock.Root()), triedb) oldTrie, err := trie.NewStateTrie(trie.StateTrieID(startHeader.Root), triedb)
if err != nil { if err != nil {
return nil, err return nil, err
} }
newTrie, err := trie.NewStateTrie(trie.StateTrieID(endBlock.Root()), triedb) newTrie, err := trie.NewStateTrie(trie.StateTrieID(endHeader.Root), triedb)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -18,25 +18,74 @@ package eth
import ( import (
"bytes" "bytes"
"crypto/ecdsa"
"fmt" "fmt"
"math/big"
"reflect" "reflect"
"slices" "slices"
"strings" "strings"
"testing" "testing"
"time"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"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/state"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"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" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"github.com/stretchr/testify/assert"
) )
var dumper = spew.ConfigState{Indent: " "} var dumper = spew.ConfigState{Indent: " "}
type Account struct {
key *ecdsa.PrivateKey
addr common.Address
}
func newAccounts(n int) (accounts []Account) {
for i := 0; i < n; i++ {
key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey)
accounts = append(accounts, Account{key: key, addr: addr})
}
slices.SortFunc(accounts, func(a, b Account) int { return a.addr.Cmp(b.addr) })
return accounts
}
// newTestBlockChain creates a new test blockchain. OBS: After test is done, teardown must be
// invoked in order to release associated resources.
func newTestBlockChain(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *core.BlockChain {
engine := ethash.NewFaker()
// Generate blocks for testing
_, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, n, generator)
// Import the canonical chain
cacheConfig := &core.CacheConfig{
TrieCleanLimit: 256,
TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute,
SnapshotLimit: 0,
Preimages: true,
TrieDirtyDisabled: true, // Archive mode
}
chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), cacheConfig, gspec, nil, engine, vm.Config{}, nil)
if err != nil {
t.Fatalf("failed to create tester chain: %v", err)
}
if n, err := chain.InsertChain(blocks); err != nil {
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
}
return chain
}
func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.Dump { func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.Dump {
result := statedb.RawDump(&state.DumpConfig{ result := statedb.RawDump(&state.DumpConfig{
SkipCode: true, SkipCode: true,
@ -224,3 +273,66 @@ func TestStorageRangeAt(t *testing.T) {
} }
} }
} }
func TestGetModifiedAccounts(t *testing.T) {
t.Parallel()
// Initialize test accounts
accounts := newAccounts(4)
genesis := &core.Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
accounts[2].addr: {Balance: big.NewInt(params.Ether)},
accounts[3].addr: {Balance: big.NewInt(params.Ether)},
},
}
genBlocks := 1
signer := types.HomesteadSigner{}
blockChain := newTestBlockChain(t, genBlocks, genesis, func(_ int, b *core.BlockGen) {
// Transfer from account[0] to account[1]
// value: 1000 wei
// fee: 0 wei
for _, account := range accounts[:3] {
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
Nonce: 0,
To: &accounts[3].addr,
Value: big.NewInt(1000),
Gas: params.TxGas,
GasPrice: b.BaseFee(),
Data: nil}),
signer, account.key)
b.AddTx(tx)
}
})
defer blockChain.Stop()
// Create a debug API instance.
api := NewDebugAPI(&Ethereum{blockchain: blockChain})
// Test GetModifiedAccountsByNumber
t.Run("GetModifiedAccountsByNumber", func(t *testing.T) {
addrs, err := api.GetModifiedAccountsByNumber(uint64(genBlocks), nil)
assert.NoError(t, err)
assert.Len(t, addrs, len(accounts)+1) // +1 for the coinbase
for _, account := range accounts {
if !slices.Contains(addrs, account.addr) {
t.Fatalf("account %s not found in modified accounts", account.addr.Hex())
}
}
})
// Test GetModifiedAccountsByHash
t.Run("GetModifiedAccountsByHash", func(t *testing.T) {
header := blockChain.GetHeaderByNumber(uint64(genBlocks))
addrs, err := api.GetModifiedAccountsByHash(header.Hash(), nil)
assert.NoError(t, err)
assert.Len(t, addrs, len(accounts)+1) // +1 for the coinbase
for _, account := range accounts {
if !slices.Contains(addrs, account.addr) {
t.Fatalf("account %s not found in modified accounts", account.addr.Hex())
}
}
})
}

View file

@ -128,7 +128,14 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
} }
log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024) log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024)
chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false) dbOptions := node.DatabaseOptions{
Cache: config.DatabaseCache,
Handles: config.DatabaseHandles,
AncientsDirectory: config.DatabaseFreezer,
EraDirectory: config.DatabaseEra,
MetricsNamespace: "eth/db/chaindata/",
}
chainDb, err := stack.OpenDatabaseWithOptions("chaindata", dbOptions)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -337,9 +344,6 @@ func makeExtraData(extra []byte) []byte {
func (s *Ethereum) APIs() []rpc.API { func (s *Ethereum) APIs() []rpc.API {
apis := ethapi.GetAPIs(s.APIBackend) apis := ethapi.GetAPIs(s.APIBackend)
// Append any APIs exposed explicitly by the consensus engine
apis = append(apis, s.engine.APIs(s.BlockChain())...)
// Append all the local APIs and return // Append all the local APIs and return
return append(apis, []rpc.API{ return append(apis, []rpc.API{
{ {

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