resolve conflict

This commit is contained in:
Sina Mahmoodi 2026-05-12 15:38:07 +00:00
commit 3266f5ad0a
108 changed files with 2496 additions and 973 deletions

View file

@ -145,7 +145,7 @@ jobs:
windows:
name: Windows Build
runs-on: "win-11"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -155,57 +155,46 @@ jobs:
go-version: 1.24
cache: false
# Note: gcc.exe only works properly if the corresponding bin/ directory is
# contained in PATH.
- name: Install cross toolchain
run: |
apt-get update
apt-get -yq --no-install-suggests --no-install-recommends install \
gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 nsis
- name: "Build (amd64)"
shell: cmd
run: |
set PATH=%GETH_MINGW%\bin;%PATH%
go run build/ci.go install -dlgo -arch amd64 -cc %GETH_MINGW%\bin\gcc.exe
env:
GETH_MINGW: 'C:\msys64\mingw64'
go run build/ci.go install -dlgo -os windows -arch amd64 -cc x86_64-w64-mingw32-gcc
- name: "Create/upload archive (amd64)"
shell: cmd
run: |
go run build/ci.go archive -arch amd64 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
go run build/ci.go archive -os windows -arch amd64 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
env:
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
- name: "Create/upload NSIS installer (amd64)"
shell: cmd
run: |
set "PATH=C:\Program Files (x86)\NSIS;%PATH%"
go run build/ci.go nsis -arch amd64 -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
del /Q build\bin\*
rm -f build/bin/*
env:
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
- name: "Build (386)"
shell: cmd
run: |
set PATH=%GETH_MINGW%\bin;%PATH%
go run build/ci.go install -dlgo -arch 386 -cc %GETH_MINGW%\bin\gcc.exe
env:
GETH_MINGW: 'C:\msys64\mingw32'
go run build/ci.go install -dlgo -os windows -arch 386 -cc i686-w64-mingw32-gcc
- name: "Create/upload archive (386)"
shell: cmd
run: |
go run build/ci.go archive -arch 386 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
go run build/ci.go archive -os windows -arch 386 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
env:
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
- name: "Create/upload NSIS installer (386)"
shell: cmd
run: |
set "PATH=C:\Program Files (x86)\NSIS;%PATH%"
go run build/ci.go nsis -arch 386 -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
del /Q build\bin\*
rm -f build/bin/*
env:
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}

View file

@ -176,6 +176,13 @@ var (
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
// an empty contract behind.
ErrNoCodeAfterDeploy = bind2.ErrNoCodeAfterDeploy
// ErrNoEventSignature is returned when a log entry has no topics.
ErrNoEventSignature = bind2.ErrNoEventSignature
// ErrEventSignatureMismatch is returned when a log's topic[0] does not match
// the expected event signature.
ErrEventSignatureMismatch = bind2.ErrEventSignatureMismatch
)
// ContractCaller defines the methods needed to allow operating with a contract on a read

View file

@ -910,7 +910,7 @@ func TestUnpackTuple(t *testing.T) {
},
},
FieldT: T{
big.NewInt(0), big.NewInt(1),
big.NewInt(0).SetBits([]big.Word{}), big.NewInt(1),
},
A: big.NewInt(1),
}
@ -919,7 +919,7 @@ func TestUnpackTuple(t *testing.T) {
if err != nil {
t.Error(err)
}
if reflect.DeepEqual(ret, expected) {
if !reflect.DeepEqual(ret, expected) {
t.Error("unexpected unpack value")
}
}

View file

@ -1,39 +0,0 @@
clone_depth: 5
version: "{branch}.{build}"
image:
- Visual Studio 2019
environment:
matrix:
- GETH_ARCH: amd64
GETH_MINGW: 'C:\msys64\mingw64'
- GETH_ARCH: 386
GETH_MINGW: 'C:\msys64\mingw32'
install:
- git submodule update --init --depth 1 --recursive
- go version
for:
# Windows builds for amd64 + 386.
- matrix:
only:
- image: Visual Studio 2019
environment:
# We use gcc from MSYS2 because it is the most recent compiler version available on
# AppVeyor. Note: gcc.exe only works properly if the corresponding bin/ directory is
# contained in PATH.
GETH_CC: '%GETH_MINGW%\bin\gcc.exe'
PATH: '%GETH_MINGW%\bin;C:\Program Files (x86)\NSIS\;%PATH%'
build_script:
- 'echo %GETH_ARCH%'
- 'echo %GETH_CC%'
- '%GETH_CC% --version'
- go run build/ci.go install -dlgo -arch %GETH_ARCH% -cc %GETH_CC%
after_build:
# Upload builds. Note that ci.go makes this a no-op PR builds.
- go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
- go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
test_script:
- go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short

View file

@ -81,6 +81,7 @@ var (
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"}
TooDeepReorg = &EngineAPIError{code: -38006, msg: "Too deep reorg"}
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}

View file

@ -276,7 +276,7 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
if data.BaseFeePerGas != nil && (data.BaseFeePerGas.Sign() == -1 || data.BaseFeePerGas.BitLen() > 256) {
return nil, fmt.Errorf("invalid baseFeePerGas: %v", data.BaseFeePerGas)
}
var blobHashes = make([]common.Hash, 0, len(txs))
var blobHashes = make([]common.Hash, 0, len(versionedHashes))
for _, tx := range txs {
blobHashes = append(blobHashes, tx.BlobHashes()...)
}

View file

@ -5,49 +5,49 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
# version:golang 1.25.9
# version:golang 1.25.10
# https://go.dev/dl/
0ec9ef8ebcea097aac37decae9f09a7218b451cd96be7d6ed513d8e4bcf909cf go1.25.9.src.tar.gz
b9ede6378a8f8d3d22bf52e68beb69ef7abdb65929ab2456020383002da15846 go1.25.9.aix-ppc64.tar.gz
92cb78fba4796e218c1accb0ea0a214ef2094c382049a244ad6505505d015fbe go1.25.9.darwin-amd64.tar.gz
9528be7329b9770631a6bd09ca2f3a73ed7332bec01d87435e75e92d8f130363 go1.25.9.darwin-arm64.tar.gz
918e44a471c5524caa52f74185064240d5eb343aa8023d604776511fc7adffa6 go1.25.9.dragonfly-amd64.tar.gz
2d67dbdfd09c6fcaa0e64485367ef43b8837ea200c663d6417183237bcddf83d go1.25.9.freebsd-386.tar.gz
9152d0c0badbfeb0c0e148e47c12bec28099d8cf2db60958810c879e0b679d07 go1.25.9.freebsd-amd64.tar.gz
437dca59604ad4a806a6a88e3d7ec1cd98ac9b402a3671629f4e553dd8b9888f go1.25.9.freebsd-arm.tar.gz
4c0fe53977412036fc8081e8d0992bbaabe4d3e1926137271ba11c2f5753300f go1.25.9.freebsd-arm64.tar.gz
d6087cdd1c084bd186132f29e0d032852a745f3c7619003d0fd5612c1fa58c8a go1.25.9.freebsd-riscv64.tar.gz
f82e49037e195cb62beae6a6ad83497157b2af5a01bad2f1dcb65df41080aabb go1.25.9.illumos-amd64.tar.gz
1e14a73bc2b19e370e0d4c57ba87aabfe8aef1e435e14d246742d48a13254f36 go1.25.9.linux-386.tar.gz
00859d7bd6defe8bf84d9db9e57b9a4467b2887c18cd93ae7460e713db774bc1 go1.25.9.linux-amd64.tar.gz
ec342e7389b7f489564ed5463c63b16cf8040023dabc7861256677165a8c0e2b go1.25.9.linux-arm64.tar.gz
7d4f0d266d871301e08ef4ac31c56e66048688893b2848392e5c600276351ee8 go1.25.9.linux-armv6l.tar.gz
f3460d901a14496bc609636e4accf9110ee1869d41c64af7e29cd567cffcf49b go1.25.9.linux-loong64.tar.gz
1da96ea449382ff96c09c55cee74815324e01d687d5ac6d2ade58244b8574306 go1.25.9.linux-mips.tar.gz
311a7f5f01f9a4bd51288b575eb619dc8e28e1fbc0cd78256a428b3ca668ff01 go1.25.9.linux-mips64.tar.gz
0b4edaf9e2ba3f0a079547effda70ec6a4b51a6ca3271a1147652c87ebcf3735 go1.25.9.linux-mips64le.tar.gz
42667340df264896f20b12261429d954e736e9772ab83ba289e68c30cf6f9628 go1.25.9.linux-mipsle.tar.gz
b9cbb3a4894b5aca6966c23452608435e8535278ef019b18d8898fbbfab67e74 go1.25.9.linux-ppc64.tar.gz
b0c41c7da1fc8d39020d65296a0dc54167afd9f76d67064e22c31ce3d839a739 go1.25.9.linux-ppc64le.tar.gz
2a630be8f854177c13e5fa75f7812c721369ecb9bd6e4c0fb1bd1c708d08b37c go1.25.9.linux-riscv64.tar.gz
0cf55136ac7eaccfc36d849054f849510ea289c2d959ffbed7b3866b4f484d17 go1.25.9.linux-s390x.tar.gz
eaf8167ff10a6a3e5dd304ef5f2e020b3a7379e76fa1011dc49c895800bf367c go1.25.9.netbsd-386.tar.gz
3cc6a861e62e23feae660984e0f2f14a2efb5d1f655900afee1d51af98919ae4 go1.25.9.netbsd-amd64.tar.gz
c2c44dca10e882c30553f4aa2ab8f6722b670fb12882378c8f461a9105d40188 go1.25.9.netbsd-arm.tar.gz
f301b71a8ec448053a5d2597df2e178120204bc9a33266c81600dd5d020a61b4 go1.25.9.netbsd-arm64.tar.gz
c4543b7fdef9707b4896810c69b4160a43ecec210af45c300f3abd78aa0c9e72 go1.25.9.openbsd-386.tar.gz
37275325e314f5ab7cf8ae65c4efc7cbfdaf20b41c6849549739b57a3ac97544 go1.25.9.openbsd-amd64.tar.gz
f9c05b6b315e979ecdd47354dd287c01708d6a88dc6ae7af74c84df8fa00df94 go1.25.9.openbsd-arm.tar.gz
4e999f42cf959ff95ca84af1ea1db3771000f5e57e157904bc2ffc72c75e29a2 go1.25.9.openbsd-arm64.tar.gz
0c7fa6c7c2b1cc13ad32fa94fc31273b4adf39c1e0f0e5dcedac158ff526af3f go1.25.9.openbsd-ppc64.tar.gz
347b33953a4b6e8df17719296f360f60878fe48a2d482ceb3637a3dfd4950065 go1.25.9.openbsd-riscv64.tar.gz
889f77d567c06832e0d332fe2458653dc66d43cded7ddbca6f72ce0ca60029cc go1.25.9.plan9-386.tar.gz
978b1f931fadec2f2516237d2649ee845d93c8eaf47dd196cfd8d26c7b2706a1 go1.25.9.plan9-amd64.tar.gz
30b9565e5ad0a212fe00990ead700c751b416eb2ef8d7c91a204945a7ff83a48 go1.25.9.plan9-arm.tar.gz
9e9125ff84ab3c3522ec758cab9540a17e9cba12bfcc34b6bf556cb89b522591 go1.25.9.solaris-amd64.tar.gz
bf40515f5f4d834fa9ead31ff75581e61a38ac27bf49840b95c5c998d321c0f6 go1.25.9.windows-386.zip
a7a710e225467b34e9e09fb432b829c86c9b2da5821ee5418f7eb2e8ae1a22cc go1.25.9.windows-amd64.zip
33cd73cf1b3ceee655ef71bc96e94006c02ae3c617fdd67ac9be3dfae3957449 go1.25.9.windows-arm64.zip
20cf04a92e5af99748e341bc8996fa28090c9ac98765fa115ec5ddf41d7af41d go1.25.10.src.tar.gz
a194e767c2ab4216a60acc068b9dbe6bf4fae05c14bb52d6bbdcb5b3ea521308 go1.25.10.aix-ppc64.tar.gz
52321165a3146cd91865ef98371506a846ed4dc4f9f1c9323e5ad90d2a411e06 go1.25.10.darwin-amd64.tar.gz
795691a425de7e7cdba3544f354dcd2cebcf52e87dc6898193878f34eb6d634f go1.25.10.darwin-arm64.tar.gz
e37b4544ba9e9e9a7ab2ed3116b3fc4d39a88da854baa5a566d9d6d3a9de7d4c go1.25.10.dragonfly-amd64.tar.gz
2a70d1fdabab637aa442ca94599a56e381238efa20cb995d5433b8579bfe482c go1.25.10.freebsd-386.tar.gz
9cdf522d87d47d82fec4a313cc4f8c3c94a7770426e8d443e4150a1f330cba71 go1.25.10.freebsd-amd64.tar.gz
6da6183633e9e59ffd9edefab68b5059c89b605596d94aaba650b1681fccd35f go1.25.10.freebsd-arm.tar.gz
7adcefeebdd05331f4d45f1ad2dddb5c53537cff6552e82f6595b3b833b95371 go1.25.10.freebsd-arm64.tar.gz
285f80a1ace21a7d94035cd753196eeada8cacd48e6396fd116ad5eb67aea957 go1.25.10.freebsd-riscv64.tar.gz
de7461bf0e5068a4f6e7f8713026d70516be6dbd5de5d21f9ced1c182f2f326e go1.25.10.illumos-amd64.tar.gz
2f574f2e2e19ead5b280fec0e7af5c81b76632685f03b6ac42dfa34c4b773c52 go1.25.10.linux-386.tar.gz
42d4f7a32316aa66591eca7e89867256057a4264451aca10570a715b3637ba70 go1.25.10.linux-amd64.tar.gz
654da1f9b50a5d1c2a85ccf8ed405aa89c06e94d18384628bf186f7712677b08 go1.25.10.linux-arm64.tar.gz
39f168f158e693887d3ad006168af1b1a3007b19c5993cae4d9d57f82f52aaf8 go1.25.10.linux-armv6l.tar.gz
05401fe5ea50ad2bafb9c797ef9bf21574b0661f19ef4d0dd66af8a0fb7323f3 go1.25.10.linux-loong64.tar.gz
d5bc2d6155d394a3aae41f21eb7c60da5595a6147aa0f30ed6b27da25e06c3f7 go1.25.10.linux-mips.tar.gz
8c64e7493e5953c3ba3153487d2fddd7f8ed142392c77f138e6792a6c1930db4 go1.25.10.linux-mips64.tar.gz
bd53aa2d558b7c1eadfc6bf01132e1859203a92f458ed7ba75b7f3230f14b095 go1.25.10.linux-mips64le.tar.gz
120b254e2e2980bb06687175db5c4064a85696c53001dc9f59934ad18f74a6bc go1.25.10.linux-mipsle.tar.gz
8a6acb21295b0ec974a44608361920ea8dbff5666631a6f556bd7d5f1d56535f go1.25.10.linux-ppc64.tar.gz
778925fdcdf9a272f823d147fad51545c3334b7ccd8652b2ccaaf2b01800280a go1.25.10.linux-ppc64le.tar.gz
b4f04ad0db48bcfea946db5323919cd21034e0bd2821a557dacd29c1b1013a4b go1.25.10.linux-riscv64.tar.gz
936b953e43921a64c12da871f76871ebbeb6d2092a7b8bdc307f5246f3c662cc go1.25.10.linux-s390x.tar.gz
061470e0bc7132146a5925a3cc28d5bc498eb1b1ff09dedcfaae10f781ff2274 go1.25.10.netbsd-386.tar.gz
63b2d50d7f8f269a9c82d42a4060e90cffb7f9102299818bb071b067aac8da8f go1.25.10.netbsd-amd64.tar.gz
c35129f68796526aa4dc4b6f481e2d995ef312aedadc88b659b945cc00e1f8f0 go1.25.10.netbsd-arm.tar.gz
2f541da4e2b298154d992d1f11bbb38c89d0821d91cc50a46776d42bb5e63bca go1.25.10.netbsd-arm64.tar.gz
2d42e569b07f1b99fdbfd008e7c22f967d165e2ce02464f46818fbed2aec43f5 go1.25.10.openbsd-386.tar.gz
0ad05960e8c9f867328151308c87f938433bec8f22f6a9437a896e22169fc840 go1.25.10.openbsd-amd64.tar.gz
099cc11473f99461c77161912740945308f08f6834980afb262c72bdc915f2d7 go1.25.10.openbsd-arm.tar.gz
bdf3335d5008c1ddc81fa94892283e4f1fee22566f5351d4e726d9f55a67c838 go1.25.10.openbsd-arm64.tar.gz
0933d418da0a61e0f29de717a77498f16b9b5b50dbe2205e20b2ed7fd4067f75 go1.25.10.openbsd-ppc64.tar.gz
191e6f3e75712f8c13d189d53b668e2cac6449f26474c1d86fbd04f6e9846f9c go1.25.10.openbsd-riscv64.tar.gz
68c053c8acd76c50fc430e92f4a86110ec3d97dd03d27b9339b4eaf793caff5f go1.25.10.plan9-386.tar.gz
42e2c46638ae22d93402e79efb40faee5c42cf7c56a01bb3ab47c6bb2512b745 go1.25.10.plan9-amd64.tar.gz
3ef1d5838b1648da16724a07b72e839ccbd7cb8899c3e0426afd6b79d494b91c go1.25.10.plan9-arm.tar.gz
631e3716017fbec06500a628d97e1155daec3593f0a7812c2ebfe8fc8c96b2ab go1.25.10.solaris-amd64.tar.gz
ddc693d2d9d7cc671ebb72d1d50aa05670f95b059b7d90440611af57976871d5 go1.25.10.windows-386.zip
ca37af2dadd8544464f1a9ca7c3886499d1cdfcb263855d0a1d71f194b2bd222 go1.25.10.windows-amd64.zip
38be57e0398bd93673d65bcae6dc7ee3cf151d7038d0dba5c60a5153022872da go1.25.10.windows-arm64.zip
# version:golangci 2.10.1
# https://github.com/golangci/golangci-lint/releases/

View file

@ -73,21 +73,9 @@ var (
"./cmd/keeper",
}
// Files that end up in the geth*.zip archive.
gethArchiveFiles = []string{
"COPYING",
executablePath("geth"),
}
// Files that end up in the geth-alltools*.zip archive.
allToolsArchiveFiles = []string{
"COPYING",
executablePath("abigen"),
executablePath("evm"),
executablePath("geth"),
executablePath("rlpdump"),
executablePath("clef"),
}
// Files that end up in the geth-alltools*.zip archive (and the NSIS installer
// dev-tools section). Order matches the historical layout produced by ci.go.
allToolsBinaries = []string{"abigen", "evm", "geth", "rlpdump", "clef"}
// Keeper build targets with their configurations
keeperTargets = []struct {
@ -180,13 +168,35 @@ var (
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
func executablePath(name string) string {
if runtime.GOOS == "windows" {
// executablePath returns the path to a built binary in GOBIN, applying the
// platform-specific extension for the given target OS.
func executablePath(name, targetOS string) string {
if targetOS == "windows" {
name += ".exe"
}
return filepath.Join(GOBIN, name)
}
// gethArchiveFiles returns the file list for the geth-{platform}-{ver}.zip
// archive, with binary paths resolved for the target OS.
func gethArchiveFiles(targetOS string) []string {
return []string{
"COPYING",
executablePath("geth", targetOS),
}
}
// allToolsArchiveFiles returns the file list for the
// geth-alltools-{platform}-{ver}.zip archive, with binary paths resolved for
// the target OS.
func allToolsArchiveFiles(targetOS string) []string {
files := []string{"COPYING"}
for _, name := range allToolsBinaries {
files = append(files, executablePath(name, targetOS))
}
return files
}
func main() {
log.SetFlags(log.Lshortfile)
@ -233,6 +243,7 @@ func main() {
func doInstall(cmdline []string) {
var (
dlgo = flag.Bool("dlgo", false, "Download Go and build with it")
targetOS = flag.String("os", runtime.GOOS, "Target OS to cross build for")
arch = flag.String("arch", "", "Architecture to cross build for")
cc = flag.String("cc", "", "C compiler to cross build with")
staticlink = flag.Bool("static", false, "Create statically-linked executable")
@ -241,7 +252,7 @@ func doInstall(cmdline []string) {
env := build.Env()
// Configure the toolchain.
tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
tc := build.GoToolchain{GOOS: *targetOS, GOARCH: *arch, CC: *cc}
if *dlgo {
csdb := download.MustLoadChecksums("build/checksums.txt")
tc.Root = build.DownloadGo(csdb)
@ -255,7 +266,7 @@ func doInstall(cmdline []string) {
}
// Configure the build.
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...)
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags, *targetOS)...)
// Show packages during build.
gobuild.Args = append(gobuild.Args, "-v")
@ -270,7 +281,7 @@ func doInstall(cmdline []string) {
// Do the build!
for _, pkg := range packages {
args := slices.Clone(gobuild.Args)
args = append(args, "-o", executablePath(path.Base(pkg)))
args = append(args, "-o", executablePath(path.Base(pkg), *targetOS))
args = append(args, pkg)
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env})
}
@ -297,7 +308,13 @@ func doInstallKeeper(cmdline []string) {
tc.GOARCH = target.GOARCH
tc.GOOS = target.GOOS
tc.CC = target.CC
gobuild := tc.Go("build", buildFlags(env, true, []string{target.Tags})...)
// An empty GOOS means "build for the host OS"; thread that through to
// buildFlags so platform-specific linker flags are picked correctly.
targetOS := target.GOOS
if targetOS == "" {
targetOS = runtime.GOOS
}
gobuild := tc.Go("build", buildFlags(env, true, []string{target.Tags}, targetOS)...)
gobuild.Dir = "./cmd/keeper"
gobuild.Args = append(gobuild.Args, "-v")
@ -307,14 +324,15 @@ func doInstallKeeper(cmdline []string) {
outputName := fmt.Sprintf("keeper-%s", target.Name)
args := slices.Clone(gobuild.Args)
args = append(args, "-o", executablePath(outputName))
args = append(args, "-o", executablePath(outputName, targetOS))
args = append(args, ".")
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env, Dir: gobuild.Dir})
}
}
// buildFlags returns the go tool flags for building.
func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (flags []string) {
// buildFlags returns the go tool flags for building. targetOS is the OS we
// are producing binaries for.
func buildFlags(env build.Environment, staticLinking bool, buildTags []string, targetOS string) (flags []string) {
var ld []string
// See https://github.com/golang/go/issues/33772#issuecomment-528176001
// We need to set --buildid to the linker here, and also pass --build-id to the
@ -326,10 +344,10 @@ func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (
}
// Strip DWARF on darwin. This used to be required for certain things,
// and there is no downside to this, so we just keep doing it.
if runtime.GOOS == "darwin" {
if targetOS == "darwin" {
ld = append(ld, "-s")
}
if runtime.GOOS == "linux" {
if targetOS == "linux" {
// Enforce the stacksize to 8M, which is the case on most platforms apart from
// alpine Linux.
// See https://sourceware.org/binutils/docs-2.23.1/ld/Options.html#Options
@ -682,12 +700,13 @@ func downloadProtoc(cachedir string) string {
// Release Packaging
func doArchive(cmdline []string) {
var (
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`)
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
ext string
targetOS = flag.String("os", runtime.GOOS, "Target OS the binaries were built for")
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`)
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
ext string
)
flag.CommandLine.Parse(cmdline)
switch *atype {
@ -701,15 +720,15 @@ func doArchive(cmdline []string) {
var (
env = build.Env()
basegeth = archiveBasename(*arch, version.Archive(env.Commit))
basegeth = archiveBasename(*targetOS, *arch, version.Archive(env.Commit))
geth = "geth-" + basegeth + ext
alltools = "geth-alltools-" + basegeth + ext
)
maybeSkipArchive(env)
if err := build.WriteArchive(geth, gethArchiveFiles); err != nil {
if err := build.WriteArchive(geth, gethArchiveFiles(*targetOS)); err != nil {
log.Fatal(err)
}
if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil {
if err := build.WriteArchive(alltools, allToolsArchiveFiles(*targetOS)); err != nil {
log.Fatal(err)
}
for _, archive := range []string{geth, alltools} {
@ -735,7 +754,11 @@ func doKeeperArchive(cmdline []string) {
maybeSkipArchive(env)
files := []string{"COPYING"}
for _, target := range keeperTargets {
files = append(files, executablePath(fmt.Sprintf("keeper-%s", target.Name)))
targetOS := target.GOOS
if targetOS == "" {
targetOS = runtime.GOOS
}
files = append(files, executablePath(fmt.Sprintf("keeper-%s", target.Name), targetOS))
}
if err := build.WriteArchive(keeper, files); err != nil {
log.Fatal(err)
@ -745,8 +768,8 @@ func doKeeperArchive(cmdline []string) {
}
}
func archiveBasename(arch string, archiveVersion string) string {
platform := runtime.GOOS + "-" + arch
func archiveBasename(targetOS, arch, archiveVersion string) string {
platform := targetOS + "-" + arch
if arch == "arm" {
platform += os.Getenv("GOARM")
}
@ -1209,13 +1232,13 @@ func doWindowsInstaller(cmdline []string) {
env := build.Env()
maybeSkipArchive(env)
// Aggregate binaries that are included in the installer
// Aggregate binaries that are included in the installer.
var (
devTools []string
allTools []string
gethTool string
)
for _, file := range allToolsArchiveFiles {
for _, file := range allToolsArchiveFiles("windows") {
if file == "COPYING" { // license, copied later
continue
}
@ -1252,16 +1275,24 @@ func doWindowsInstaller(cmdline []string) {
if env.Commit != "" {
ver[2] += "-" + env.Commit[:8]
}
installer, err := filepath.Abs("geth-" + archiveBasename(*arch, version.Archive(env.Commit)) + ".exe")
installer, err := filepath.Abs("geth-" + archiveBasename("windows", *arch, version.Archive(env.Commit)) + ".exe")
if err != nil {
log.Fatalf("Failed to convert installer file path: %v", err)
}
build.MustRunCommand("makensis.exe",
"/DOUTPUTFILE="+installer,
"/DMAJORVERSION="+ver[0],
"/DMINORVERSION="+ver[1],
"/DBUILDVERSION="+ver[2],
"/DARCH="+*arch,
// makensis on Windows is "makensis.exe" with /D-style defines; on Linux
// (and other Unixes) the binary is "makensis" and accepts -D.
makensisCmd := "makensis"
defineFlag := "-D"
if runtime.GOOS == "windows" {
makensisCmd = "makensis.exe"
defineFlag = "/D"
}
build.MustRunCommand(makensisCmd,
defineFlag+"OUTPUTFILE="+installer,
defineFlag+"MAJORVERSION="+ver[0],
defineFlag+"MINORVERSION="+ver[1],
defineFlag+"BUILDVERSION="+ver[2],
defineFlag+"ARCH="+*arch,
filepath.Join(*workdir, "geth.nsi"),
)
// Sign and publish installer.

View file

@ -215,7 +215,7 @@ func generate(c *cli.Context) error {
code string
err error
)
if c.IsSet(v2Flag.Name) {
if c.Bool(v2Flag.Name) {
code, err = abigen.BindV2(types, abis, bins, c.String(pkgFlag.Name), libs, aliases)
} else {
code, err = abigen.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases)

View file

@ -194,7 +194,7 @@ func formatAttrString(v rlp.RawValue) (string, bool) {
func formatAttrIP(v rlp.RawValue) (string, bool) {
content, _, err := rlp.SplitString(v)
if err != nil || len(content) != 4 && len(content) != 6 {
if err != nil || len(content) != 4 && len(content) != 16 {
return "", false
}
return net.IP(content).String(), true

View file

@ -337,9 +337,6 @@ func checkAccumulator(e era.Era) error {
// accumulation across the entire set and are verified at the end.
for it.Next() {
// 1) next() walks the block index, so we're able to implicitly verify it.
if it.Error() != nil {
return fmt.Errorf("error reading block %d: %w", it.Number(), it.Error())
}
block, receipts, err := it.BlockAndReceipts()
if err != nil {
return fmt.Errorf("error reading block %d: %w", it.Number(), err)

View file

@ -17,6 +17,7 @@
package t8ntool
import (
"context"
"encoding/json"
"fmt"
stdmath "math"
@ -331,27 +332,14 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
}
// Gather the execution-layer triggered requests.
var requests [][]byte
if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) {
requests = [][]byte{}
// EIP-6110
var allLogs []*types.Log
for _, receipt := range receipts {
allLogs = append(allLogs, receipt.Logs...)
}
if err := core.ParseDepositLogs(&requests, allLogs, chainConfig); err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
}
// EIP-7002
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process withdrawal requests: %v", err))
}
// EIP-7251
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process consolidation requests: %v", err))
}
var allLogs []*types.Log
for _, receipt := range receipts {
allLogs = append(allLogs, receipt.Logs...)
}
requests, err := core.PostExecution(context.Background(), chainConfig, vmContext.BlockNumber, vmContext.Time, allLogs, evm)
if err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("failed to process post-execution: %v", err))
}
// Commit block
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), chainConfig.IsCancun(vmContext.BlockNumber, vmContext.Time))
if err != nil {

View file

@ -133,7 +133,7 @@ func Transaction(ctx *cli.Context) error {
}
// Check intrinsic gas
rules := chainConfig.Rules(common.Big0, true, 0)
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
if err != nil {
r.Error = err
results = append(results, r)
@ -147,7 +147,7 @@ func Transaction(ctx *cli.Context) error {
}
// For Prague txs, validate the floor data gas.
if rules.IsPrague {
floorDataGas, err := core.FloorDataGas(rules, tx.Data())
floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
if err != nil {
r.Error = err
results = append(results, r)

View file

@ -546,7 +546,7 @@ func BinKeys(ctx *cli.Context) error {
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.UBTDefaults)
defer db.Close()
bt, err := genBinTrieFromAlloc(alloc, db)
bt, err := genBinTrieFromAlloc(alloc, db, triedb.UBTDefaults.BinTrieGroupDepth)
if err != nil {
return fmt.Errorf("error generating bt: %w", err)
}
@ -590,7 +590,7 @@ func BinTrieRoot(ctx *cli.Context) error {
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.UBTDefaults)
defer db.Close()
bt, err := genBinTrieFromAlloc(alloc, db)
bt, err := genBinTrieFromAlloc(alloc, db, triedb.UBTDefaults.BinTrieGroupDepth)
if err != nil {
return fmt.Errorf("error generating bt: %w", err)
}
@ -600,8 +600,8 @@ func BinTrieRoot(ctx *cli.Context) error {
}
// TODO(@CPerezz): Should this go to `bintrie` module?
func genBinTrieFromAlloc(alloc core.GenesisAlloc, db database.NodeDatabase) (*bintrie.BinaryTrie, error) {
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, db)
func genBinTrieFromAlloc(alloc core.GenesisAlloc, db database.NodeDatabase, groupDepth int) (*bintrie.BinaryTrie, error) {
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, db, groupDepth)
if err != nil {
return nil, err
}

View file

@ -321,7 +321,7 @@ func runCmd(ctx *cli.Context) error {
// don't mutate the state!
runtimeConfig.State = prestate.Copy()
output, _, gasLeft, err := runtime.Create(input, &runtimeConfig)
return output, gasLeft, err
return output, initialGas - gasLeft, err
}
} else {
if len(code) > 0 {

View file

@ -151,7 +151,7 @@ func convertToBinaryTrie(ctx *cli.Context) error {
})
defer destTriedb.Close()
binTrie, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
binTrie, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb, ctx.Int(utils.BinTrieGroupDepthFlag.Name))
if err != nil {
return fmt.Errorf("failed to create binary trie: %w", err)
}
@ -319,7 +319,7 @@ func commitBinaryTrie(bt *bintrie.BinaryTrie, currentRoot common.Hash, destDB *t
runtime.GC()
debug.FreeOSMemory()
bt, err := bintrie.NewBinaryTrie(newRoot, destDB)
bt, err := bintrie.NewBinaryTrie(newRoot, destDB, bt.GroupDepth())
if err != nil {
return nil, common.Hash{}, fmt.Errorf("failed to reload binary trie: %w", err)
}

View file

@ -87,7 +87,7 @@ func TestBintrieConvert(t *testing.T) {
})
defer destTriedb.Close()
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb, 8)
if err != nil {
t.Fatalf("failed to create binary trie: %v", err)
}
@ -98,7 +98,7 @@ func TestBintrieConvert(t *testing.T) {
}
t.Logf("Binary trie root: %x", currentRoot)
bt2, err := bintrie.NewBinaryTrie(currentRoot, destTriedb)
bt2, err := bintrie.NewBinaryTrie(currentRoot, destTriedb, 8)
if err != nil {
t.Fatalf("failed to reload binary trie: %v", err)
}
@ -194,7 +194,7 @@ func TestBintrieConvertDeleteSource(t *testing.T) {
PathDB: pathdb.Defaults,
})
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb, 8)
if err != nil {
t.Fatalf("failed to create binary trie: %v", err)
}
@ -209,7 +209,7 @@ func TestBintrieConvertDeleteSource(t *testing.T) {
}
srcTriedb2.Close()
bt2, err := bintrie.NewBinaryTrie(newRoot, destTriedb)
bt2, err := bintrie.NewBinaryTrie(newRoot, destTriedb, 8)
if err != nil {
t.Fatalf("failed to reload binary trie after deletion: %v", err)
}

View file

@ -325,7 +325,7 @@ func dumpGenesis(ctx *cli.Context) error {
var genesis *core.Genesis
if utils.IsNetworkPreset(ctx) {
genesis = utils.MakeGenesis(ctx)
} else if ctx.IsSet(utils.DeveloperFlag.Name) && !ctx.IsSet(utils.DataDirFlag.Name) {
} else if ctx.Bool(utils.DeveloperFlag.Name) && !ctx.IsSet(utils.DataDirFlag.Name) {
genesis = core.DeveloperGenesisBlock(11_500_000, nil)
}

View file

@ -38,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/syncer"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/internal/telemetry/tracesetup"
"github.com/ethereum/go-ethereum/internal/version"
@ -269,25 +270,28 @@ func makeFullNode(ctx *cli.Context) *node.Node {
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)
// Configure GraphQL if requested.
if ctx.IsSet(utils.GraphQLEnabledFlag.Name) {
if ctx.Bool(utils.GraphQLEnabledFlag.Name) {
utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node)
}
// Add the Ethereum Stats daemon if requested.
if cfg.Ethstats.URL != "" {
utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
}
// Configure synchronization override service
var synctarget common.Hash
syncConfig := syncer.Config{
ExitWhenSynced: ctx.Bool(utils.ExitWhenSyncedFlag.Name),
}
if ctx.IsSet(utils.SyncTargetFlag.Name) {
target := ctx.String(utils.SyncTargetFlag.Name)
if !common.IsHexHash(target) {
utils.Fatalf("sync target hash is not a valid hex hash: %s", target)
}
synctarget = common.HexToHash(target)
syncConfig.TargetBlock = common.HexToHash(target)
}
utils.RegisterSyncOverrideService(stack, eth, synctarget, ctx.Bool(utils.ExitWhenSyncedFlag.Name))
utils.RegisterSyncOverrideService(stack, eth, syncConfig)
if ctx.IsSet(utils.DeveloperFlag.Name) {
if ctx.Bool(utils.DeveloperFlag.Name) {
// Start dev mode.
simBeacon, err := catalyst.NewSimulatedBeacon(ctx.Uint64(utils.DeveloperPeriodFlag.Name), cfg.Eth.Miner.PendingFeeRecipient, eth)
if err != nil {

View file

@ -22,13 +22,10 @@ import (
"os"
"slices"
"sort"
"time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console/prompt"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/flags"
@ -95,6 +92,7 @@ var (
utils.StateHistoryFlag,
utils.TrienodeHistoryFlag,
utils.TrienodeHistoryFullValueCheckpointFlag,
utils.BinTrieGroupDepthFlag,
utils.LightKDFFlag,
utils.EthRequiredBlocksFlag,
utils.LegacyWhitelistFlag, // deprecated
@ -386,28 +384,4 @@ func startNode(ctx *cli.Context, stack *node.Node, isConsole bool) {
}
}
}()
// Spawn a standalone goroutine for status synchronization monitoring,
// close the node when synchronization is complete if user required.
if ctx.Bool(utils.ExitWhenSyncedFlag.Name) {
go func() {
sub := stack.EventMux().Subscribe(downloader.DoneEvent{})
defer sub.Unsubscribe()
for {
event := <-sub.Chan()
if event == nil {
continue
}
done, ok := event.Data.(downloader.DoneEvent)
if !ok {
continue
}
if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute {
log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(),
"age", common.PrettyAge(timestamp))
stack.Close()
}
}
}()
}
}

View file

@ -297,6 +297,12 @@ var (
Value: ethconfig.Defaults.EnableStateSizeTracking,
Category: flags.StateCategory,
}
BinTrieGroupDepthFlag = &cli.IntFlag{
Name: "bintrie.groupdepth",
Usage: "Number of levels per serialized group in binary trie (1-8, default 5). Lower values create smaller groups with more nodes.",
Value: 5,
Category: flags.StateCategory,
}
StateHistoryFlag = &cli.Uint64Flag{
Name: "history.state",
Usage: "Number of recent blocks to retain state history for, only relevant in state.scheme=path (default = 90,000 blocks, 0 = entire chain)",
@ -1817,6 +1823,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(TrienodeHistoryFullValueCheckpointFlag.Name) {
cfg.NodeFullValueCheckpoint = uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name))
}
if ctx.IsSet(BinTrieGroupDepthFlag.Name) {
cfg.BinTrieGroupDepth = ctx.Int(BinTrieGroupDepthFlag.Name)
}
if ctx.IsSet(StateSchemeFlag.Name) {
cfg.StateScheme = ctx.String(StateSchemeFlag.Name)
}
@ -2228,13 +2237,13 @@ func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconf
}
// RegisterSyncOverrideService adds the synchronization override service into node.
func RegisterSyncOverrideService(stack *node.Node, eth *eth.Ethereum, target common.Hash, exitWhenSynced bool) {
if target != (common.Hash{}) {
log.Info("Registered sync override service", "hash", target, "exitWhenSynced", exitWhenSynced)
func RegisterSyncOverrideService(stack *node.Node, eth *eth.Ethereum, config syncer.Config) {
if config.TargetBlock != (common.Hash{}) {
log.Info("Registered sync override service", "hash", config.TargetBlock, "exitWhenSynced", config.ExitWhenSynced)
} else {
log.Info("Registered sync override service")
}
syncer.Register(stack, eth, target, exitWhenSynced)
syncer.Register(stack, eth, config)
}
// SetupMetrics configures the metrics system.
@ -2433,6 +2442,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
StateHistory: ctx.Uint64(StateHistoryFlag.Name),
TrienodeHistory: ctx.Int64(TrienodeHistoryFlag.Name),
NodeFullValueCheckpoint: uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name)),
BinTrieGroupDepth: ctx.Int(BinTrieGroupDepthFlag.Name),
// Disable transaction indexing/unindexing.
TxLookupLimit: -1,

View file

@ -204,6 +204,10 @@ func (b *Big) ToInt() *big.Int {
return (*big.Int)(b)
}
func (b *Big) ToUint256() (*uint256.Int, bool) {
return uint256.FromBig((*big.Int)(b))
}
// String returns the hex encoding of b.
func (b *Big) String() string {
return EncodeBig(b.ToInt())

View file

@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
data := make([]byte, nbytes)
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
cost, _ := IntrinsicGas(data, nil, nil, false, false, false, false)
cost, _ := IntrinsicGas(data, nil, nil, false, false, false, false, false)
signer := gen.Signer()
gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil {

View file

@ -63,12 +63,12 @@ var (
func TestProcessUBT(t *testing.T) {
var (
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true)
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true, false)
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
// will not contain that copied data.
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true)
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true, false)
signer = types.LatestSigner(testUBTChainConfig)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
@ -92,6 +92,7 @@ func TestProcessUBT(t *testing.T) {
// genesis := gspec.MustCommit(bcdb, triedb)
options := DefaultConfig().WithStateScheme(rawdb.PathScheme)
options.SnapshotLimit = 0
options.BinTrieGroupDepth = triedb.DefaultBinTrieGroupDepth
blockchain, _ := NewBlockChain(bcdb, gspec, beacon.New(ethash.NewFaker()), options)
defer blockchain.Stop()
@ -218,6 +219,7 @@ func TestProcessParentBlockHash(t *testing.T) {
t.Run("UBT", func(t *testing.T) {
db := rawdb.NewMemoryDatabase()
cacheConfig := DefaultConfig().WithStateScheme(rawdb.PathScheme)
cacheConfig.BinTrieGroupDepth = triedb.DefaultBinTrieGroupDepth
cacheConfig.SnapshotLimit = 0
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true))
statedb, _ := state.New(types.EmptyBinaryHash, state.NewDatabase(triedb, nil))

View file

@ -170,9 +170,10 @@ type BlockChainConfig struct {
TrieNoAsyncFlush bool // Whether the asynchronous buffer flushing is disallowed
TrieJournalDirectory string // Directory path to the journal used for persisting trie data across node restarts
Preimages bool // Whether to store preimage of trie key to the disk
StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top
ArchiveMode bool // Whether to enable the archive mode
Preimages bool // Whether to store preimage of trie key to the disk
StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top
ArchiveMode bool // Whether to enable the archive mode
BinTrieGroupDepth int // Number of levels per serialized group in binary trie (1-8)
// Number of blocks from the chain head for which state histories are retained.
// If set to 0, all state histories across the entire chain will be retained;
@ -260,8 +261,9 @@ func (cfg BlockChainConfig) WithNoAsyncFlush(on bool) *BlockChainConfig {
// triedbConfig derives the configures for trie database.
func (cfg *BlockChainConfig) triedbConfig(isUBT bool) *triedb.Config {
config := &triedb.Config{
Preimages: cfg.Preimages,
IsUBT: isUBT,
Preimages: cfg.Preimages,
IsUBT: isUBT,
BinTrieGroupDepth: cfg.BinTrieGroupDepth,
}
if cfg.StateScheme == rawdb.HashScheme {
config.HashDB = &hashdb.Config{
@ -1186,6 +1188,7 @@ func (bc *BlockChain) SnapSyncComplete(hash common.Hash) error {
}
// If all checks out, manually set the head block.
rawdb.WriteHeadBlockHash(bc.db, hash)
bc.currentBlock.Store(block.Header())
headBlockGauge.Update(int64(block.NumberU64()))
@ -2596,8 +2599,13 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
blockReorgAddMeter.Mark(int64(len(newChain)))
} else {
// len(newChain) == 0 && len(oldChain) > 0
// rewind the canonical chain to a lower point.
log.Error("Impossible reorg, please file an issue", "oldnum", oldHead.Number, "oldhash", oldHead.Hash(), "oldblocks", len(oldChain), "newnum", newHead.Number, "newhash", newHead.Hash(), "newblocks", len(newChain))
// Rewind the canonical chain to a lower point. In EPBs we can reorg to
// a parent of the head within 32 blocks.
if len(oldChain) > 32 {
log.Error("Impossible reorg, please file an issue", "oldnum", oldHead.Number, "oldhash", oldHead.Hash(), "oldblocks", len(oldChain))
} else {
log.Info("Shorten chain", "del", len(oldChain), "number", oldHead.Number, "hash", oldHead.Hash())
}
}
// Acquire the tx-lookup lock before mutation. This step is essential
// as the txlookups should be changed atomically, and all subsequent

View file

@ -17,6 +17,7 @@
package core
import (
"context"
"fmt"
"math/big"
@ -314,28 +315,17 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) {
// off the statedb before executing the system calls.
statedb = statedb.Copy()
}
var blockLogs []*types.Log
for _, r := range b.receipts {
blockLogs = append(blockLogs, r.Logs...)
}
// TODO use the shared EVM throughout the entire generation cycle
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
if b.cm.config.IsPrague(b.header.Number, b.header.Time) {
requests = [][]byte{}
// EIP-6110 deposits
var blockLogs []*types.Log
for _, r := range b.receipts {
blockLogs = append(blockLogs, r.Logs...)
}
if err := ParseDepositLogs(&requests, blockLogs, b.cm.config); err != nil {
panic(fmt.Sprintf("failed to parse deposit log: %v", err))
}
// create EVM for system calls
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
// EIP-7002
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
panic(fmt.Sprintf("could not process withdrawal requests: %v", err))
}
// EIP-7251
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
panic(fmt.Sprintf("could not process consolidation requests: %v", err))
}
requests, err := PostExecution(context.Background(), b.cm.config, b.header.Number, b.header.Time, blockLogs, evm)
if err != nil {
panic(fmt.Sprintf("failed to run post-execution: %v", err))
}
return requests
}

View file

@ -87,7 +87,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
func NewEVMTxContext(msg *Message) vm.TxContext {
ctx := vm.TxContext{
Origin: msg.From,
GasPrice: uint256.MustFromBig(msg.GasPrice),
GasPrice: msg.GasPrice,
BlobHashes: msg.BlobHashes,
}
return ctx

View file

@ -136,8 +136,9 @@ func hashAlloc(ga *types.GenesisAlloc, isUBT bool) (common.Hash, error) {
var config *triedb.Config
if isUBT {
config = &triedb.Config{
PathDB: pathdb.Defaults,
IsUBT: true,
PathDB: pathdb.Defaults,
IsUBT: true,
BinTrieGroupDepth: triedb.UBTDefaults.BinTrieGroupDepth,
}
}
// Create an ephemeral in-memory database for computing hash,

View file

@ -261,9 +261,9 @@ func newDbConfig(scheme string) *triedb.Config {
return &triedb.Config{PathDB: &config}
}
func TestVerkleGenesisCommit(t *testing.T) {
var verkleTime uint64 = 0
verkleConfig := &params.ChainConfig{
func TestBinaryGenesisCommit(t *testing.T) {
var ubtTime uint64 = 0
ubtConfig := &params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: nil,
@ -281,11 +281,11 @@ func TestVerkleGenesisCommit(t *testing.T) {
ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0),
MergeNetsplitBlock: nil,
ShanghaiTime: &verkleTime,
CancunTime: &verkleTime,
PragueTime: &verkleTime,
OsakaTime: &verkleTime,
UBTTime: &verkleTime,
ShanghaiTime: &ubtTime,
CancunTime: &ubtTime,
PragueTime: &ubtTime,
OsakaTime: &ubtTime,
UBTTime: &ubtTime,
TerminalTotalDifficulty: big.NewInt(0),
EnableUBTAtGenesis: true,
Ethash: nil,
@ -300,8 +300,8 @@ func TestVerkleGenesisCommit(t *testing.T) {
genesis := &Genesis{
BaseFee: big.NewInt(params.InitialBaseFee),
Config: verkleConfig,
Timestamp: verkleTime,
Config: ubtConfig,
Timestamp: ubtTime,
Difficulty: big.NewInt(0),
Alloc: types.GenesisAlloc{
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
@ -320,17 +320,18 @@ func TestVerkleGenesisCommit(t *testing.T) {
config.NoAsyncFlush = true
triedb := triedb.NewDatabase(db, &triedb.Config{
IsUBT: true,
PathDB: &config,
IsUBT: true,
PathDB: &config,
BinTrieGroupDepth: triedb.DefaultBinTrieGroupDepth,
})
block := genesis.MustCommit(db, triedb)
if !bytes.Equal(block.Root().Bytes(), expected) {
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, block.Root())
}
// Test that the trie is verkle
// Test that the trie is a unified binary trie
if !triedb.IsUBT() {
t.Fatalf("expected trie to be verkle")
t.Fatalf("expected trie to be a unified binary trie")
}
vdb := rawdb.NewTable(db, string(rawdb.VerklePrefix))
if !rawdb.HasAccountTrieNode(vdb, nil) {

View file

@ -96,7 +96,7 @@ func (db *UBTDatabase) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Rea
// OpenTrie opens the main account trie at a specific root hash.
func (db *UBTDatabase) OpenTrie(root common.Hash) (Trie, error) {
return bintrie.NewBinaryTrie(root, db.triedb)
return bintrie.NewBinaryTrie(root, db.triedb, db.triedb.BinTrieGroupDepth())
}
// OpenStorageTrie opens the storage trie of an account. In binary trie mode,

View file

@ -255,7 +255,7 @@ type ubtTrieReader struct {
// newUBTTrieReader constructs a Unified-binary-trie reader of the specific state.
// An error will be returned if the associated trie specified by root is not existent.
func newUBTTrieReader(root common.Hash, db *triedb.Database) (*ubtTrieReader, error) {
binTrie, binErr := bintrie.NewBinaryTrie(root, db)
binTrie, binErr := bintrie.NewBinaryTrie(root, db, db.BinTrieGroupDepth())
if binErr != nil {
return nil, binErr
}

View file

@ -441,7 +441,7 @@ func TestStorageIteratorTraversalValues(t *testing.T) {
if i%8 == 0 {
e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
}
if i > 50 || i < 85 {
if i > 50 && i < 85 {
f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
}
if i%64 == 0 {

View file

@ -1355,7 +1355,7 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorag
// The reader update must be performed as the final step, otherwise,
// the new state would not be visible before db.commit.
s.reader, _ = s.db.Reader(s.originalRoot)
s.reader, err = s.db.Reader(s.originalRoot)
return ret, err
}

View file

@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/internal/telemetry"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256"
)
// StateProcessor is a basic Processor, which takes care of transitioning
@ -75,31 +76,21 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
if hooks := cfg.Tracer; hooks != nil {
tracingStateDB = state.NewHookedState(statedb, hooks)
}
// Mutate the block and state according to any hard-fork specs
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 {
misc.ApplyDAOHardFork(tracingStateDB)
}
var (
context vm.BlockContext
context = NewEVMBlockContext(header, p.chain, nil)
signer = types.MakeSigner(config, header.Number, header.Time)
evm = vm.NewEVM(context, tracingStateDB, config, cfg)
)
// Apply pre-execution system calls.
context = NewEVMBlockContext(header, p.chain, nil)
evm := vm.NewEVM(context, tracingStateDB, config, cfg)
defer evm.Release()
if jumpDestCache != nil {
evm.SetJumpDestCache(jumpDestCache)
}
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, evm)
}
if config.IsPrague(block.Number(), block.Time()) || config.IsUBT(block.Number(), block.Time()) {
ProcessParentBlockHash(block.ParentHash(), evm)
}
// Run the pre-execution system calls
PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time())
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
@ -121,11 +112,11 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
allLogs = append(allLogs, receipt.Logs...)
spanEnd(nil)
}
requests, err := postExecution(ctx, config, block, allLogs, evm)
// Run the post-execution system calls
requests, err := PostExecution(ctx, config, block.Number(), block.Time(), allLogs, evm)
if err != nil {
return nil, err
}
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body())
@ -137,28 +128,44 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
}, nil
}
// postExecution processes the post-execution system calls if Prague is enabled.
func postExecution(ctx context.Context, config *params.ChainConfig, block *types.Block, allLogs []*types.Log, evm *vm.EVM) (requests [][]byte, err error) {
// PreExecution processes pre-execution system calls.
func PreExecution(ctx context.Context, beaconRoot *common.Hash, parent common.Hash, config *params.ChainConfig, evm *vm.EVM, number *big.Int, time uint64) {
_, _, spanEnd := telemetry.StartSpan(ctx, "core.preExecution")
defer spanEnd(nil)
// EIP-4788
if beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, evm)
}
// EIP-2935
if config.IsPrague(number, time) || config.IsUBT(number, time) {
ProcessParentBlockHash(parent, evm)
}
}
// PostExecution processes post-execution system calls when Prague is enabled.
// If Prague is not activated, it returns null requests to differentiate from
// empty requests.
func PostExecution(ctx context.Context, config *params.ChainConfig, number *big.Int, time uint64, allLogs []*types.Log, evm *vm.EVM) (requests [][]byte, err error) {
_, _, spanEnd := telemetry.StartSpan(ctx, "core.postExecution")
defer spanEnd(&err)
// Read requests if Prague is enabled.
if config.IsPrague(block.Number(), block.Time()) {
if config.IsPrague(number, time) {
requests = [][]byte{}
// EIP-6110
if err := ParseDepositLogs(&requests, allLogs, config); err != nil {
return requests, fmt.Errorf("failed to parse deposit logs: %w", err)
return nil, fmt.Errorf("failed to parse deposit logs: %w", err)
}
// EIP-7002
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
return requests, fmt.Errorf("failed to process withdrawal queue: %w", err)
return nil, fmt.Errorf("failed to process withdrawal queue: %w", err)
}
// EIP-7251
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
return requests, fmt.Errorf("failed to process consolidation queue: %w", err)
return nil, fmt.Errorf("failed to process consolidation queue: %w", err)
}
}
return requests, nil
}
@ -257,9 +264,9 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
msg := &Message{
From: params.SystemAddress,
GasLimit: 30_000_000,
GasPrice: common.Big0,
GasFeeCap: common.Big0,
GasTipCap: common.Big0,
GasPrice: uint256.NewInt(0),
GasFeeCap: uint256.NewInt(0),
GasTipCap: uint256.NewInt(0),
To: &params.BeaconRootsAddress,
Data: beaconRoot[:],
}
@ -284,9 +291,9 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
msg := &Message{
From: params.SystemAddress,
GasLimit: 30_000_000,
GasPrice: common.Big0,
GasFeeCap: common.Big0,
GasTipCap: common.Big0,
GasPrice: uint256.NewInt(0),
GasFeeCap: uint256.NewInt(0),
GasTipCap: uint256.NewInt(0),
To: &params.HistoryStorageAddress,
Data: prevHash.Bytes(),
}
@ -324,9 +331,9 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
msg := &Message{
From: params.SystemAddress,
GasLimit: 30_000_000,
GasPrice: common.Big0,
GasFeeCap: common.Big0,
GasTipCap: common.Big0,
GasPrice: uint256.NewInt(0),
GasFeeCap: uint256.NewInt(0),
GasTipCap: uint256.NewInt(0),
To: &addr,
}
evm.SetTxContext(NewEVMTxContext(msg))

View file

@ -68,7 +68,7 @@ func (result *ExecutionResult) Revert() []byte {
}
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (vm.GasCosts, error) {
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860, isAmsterdam bool) (vm.GasCosts, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
@ -107,8 +107,32 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
}
}
if accessList != nil {
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
addresses := uint64(len(accessList))
storageKeys := uint64(accessList.StorageKeys())
if (math.MaxUint64-gas)/params.TxAccessListAddressGas < addresses {
return vm.GasCosts{}, ErrGasUintOverflow
}
gas += addresses * params.TxAccessListAddressGas
if (math.MaxUint64-gas)/params.TxAccessListStorageKeyGas < storageKeys {
return vm.GasCosts{}, ErrGasUintOverflow
}
gas += storageKeys * params.TxAccessListStorageKeyGas
// EIP-7981: access list data is charged in addition to the base charge.
if isAmsterdam {
const (
addressCost = common.AddressLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
storageKeyCost = common.HashLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
)
if (math.MaxUint64-gas)/addressCost < addresses {
return vm.GasCosts{}, ErrGasUintOverflow
}
gas += addresses * addressCost
if (math.MaxUint64-gas)/storageKeyCost < storageKeys {
return vm.GasCosts{}, ErrGasUintOverflow
}
gas += storageKeys * storageKeyCost
}
}
if authList != nil {
gas += uint64(len(authList)) * params.CallNewAccountGas
@ -117,7 +141,7 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
}
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
func FloorDataGas(rules params.Rules, data []byte) (uint64, error) {
func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList) (uint64, error) {
var (
tokens uint64
tokenCost uint64
@ -125,15 +149,41 @@ func FloorDataGas(rules params.Rules, data []byte) (uint64, error) {
if rules.IsAmsterdam {
// EIP-7976 changes how calldata is priced.
// From 10/40 to 64/64 for zero/non-zero bytes.
tokens = uint64(len(data)) * params.TxTokenPerNonZeroByte
tokenCost = params.TxCostFloorPerToken7976
dataLen := uint64(len(data))
if math.MaxUint64/params.TxTokenPerNonZeroByte < dataLen {
return 0, ErrGasUintOverflow
}
tokens = dataLen * params.TxTokenPerNonZeroByte
// EIP-7981 adds additional tokens for every entry in the accesslist
const addressTokenCost = uint64(common.AddressLength) * params.TxTokenPerNonZeroByte
addresses := uint64(len(accessList))
if (math.MaxUint64-tokens)/addressTokenCost < addresses {
return 0, ErrGasUintOverflow
}
tokens += addresses * addressTokenCost
const storageKeyTokenCost = uint64(common.HashLength) * params.TxTokenPerNonZeroByte
storageKeys := uint64(accessList.StorageKeys())
if (math.MaxUint64-tokens)/storageKeyTokenCost < storageKeys {
return 0, ErrGasUintOverflow
}
tokens += storageKeys * storageKeyTokenCost
} else {
var (
z = uint64(bytes.Count(data, []byte{0}))
nz = uint64(len(data)) - z
)
// Pre-Amsterdam
tokens = nz*params.TxTokenPerNonZeroByte + z
if math.MaxUint64/params.TxTokenPerNonZeroByte < nz {
return 0, ErrGasUintOverflow
}
tokens = nz * params.TxTokenPerNonZeroByte
if math.MaxUint64-tokens < z {
return 0, ErrGasUintOverflow
}
tokens += z
tokenCost = params.TxCostFloorPerToken
}
@ -160,14 +210,14 @@ type Message struct {
To *common.Address
From common.Address
Nonce uint64
Value *big.Int
Value *uint256.Int
GasLimit uint64
GasPrice *big.Int
GasFeeCap *big.Int
GasTipCap *big.Int
GasPrice *uint256.Int
GasFeeCap *uint256.Int
GasTipCap *uint256.Int
Data []byte
AccessList types.AccessList
BlobGasFeeCap *big.Int
BlobGasFeeCap *uint256.Int
BlobHashes []common.Hash
SetCodeAuthorizations []types.SetCodeAuthorization
@ -188,32 +238,64 @@ type Message struct {
// TransactionToMessage converts a transaction into a Message.
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
from, err := types.Sender(s, tx)
if err != nil {
return nil, err
}
gasPrice, overflow := uint256.FromBig(tx.GasPrice())
if overflow {
return nil, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
from.Hex(), tx.GasPrice().BitLen())
}
txGasFeeCap := tx.GasFeeCap()
gasFeeCap, overflow := uint256.FromBig(txGasFeeCap)
if overflow {
return nil, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
from.Hex(), tx.GasFeeCap().BitLen())
}
txGasTipCap := tx.GasTipCap()
gasTipCap, overflow := uint256.FromBig(txGasTipCap)
if overflow {
return nil, fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
from.Hex(), tx.GasTipCap().BitLen())
}
value, overflow := uint256.FromBig(tx.Value())
if overflow {
return nil, fmt.Errorf("value exceeds 256 bits: address %v", from.Hex())
}
blobGasFeeCap, overflow := uint256.FromBig(tx.BlobGasFeeCap())
if overflow {
return nil, fmt.Errorf("blobGasFeeCap exceeds 256 bits: address %v", from.Hex())
}
msg := &Message{
From: from,
Nonce: tx.Nonce(),
GasLimit: tx.Gas(),
GasPrice: tx.GasPrice(),
GasFeeCap: tx.GasFeeCap(),
GasTipCap: tx.GasTipCap(),
GasPrice: gasPrice,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
To: tx.To(),
Value: tx.Value(),
Value: value,
Data: tx.Data(),
AccessList: tx.AccessList(),
SetCodeAuthorizations: tx.SetCodeAuthorizations(),
SkipNonceChecks: false,
SkipTransactionChecks: false,
BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(),
BlobGasFeeCap: blobGasFeeCap,
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
msg.GasPrice = msg.GasPrice.Add(msg.GasTipCap, baseFee)
if msg.GasPrice.Cmp(msg.GasFeeCap) > 0 {
msg.GasPrice = msg.GasFeeCap
effectiveGasPrice := new(big.Int).Add(baseFee, txGasTipCap)
if effectiveGasPrice.Cmp(txGasFeeCap) > 0 {
effectiveGasPrice = txGasFeeCap
}
// EffectiveGasPrice is already capped by txGasFeeCap, therefore
// the overflow check is not required.
msg.GasPrice = uint256.MustFromBig(effectiveGasPrice)
}
var err error
msg.From, err = types.Sender(s, tx)
return msg, err
return msg, nil
}
// ApplyMessage computes the new state by applying the given message
@ -283,32 +365,55 @@ func (st *stateTransition) to() common.Address {
}
func (st *stateTransition) buyGas() error {
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
mgval.Mul(mgval, st.msg.GasPrice)
balanceCheck := new(big.Int).Set(mgval)
mgval := new(uint256.Int).SetUint64(st.msg.GasLimit)
_, overflow := mgval.MulOverflow(mgval, st.msg.GasPrice)
if overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
balanceCheck := new(uint256.Int).Set(mgval)
if st.msg.GasFeeCap != nil {
balanceCheck.SetUint64(st.msg.GasLimit)
balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
if _, overflow := balanceCheck.MulOverflow(balanceCheck, st.msg.GasFeeCap); overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
}
if st.msg.Value != nil {
if _, overflow := balanceCheck.AddOverflow(balanceCheck, st.msg.Value); overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
}
balanceCheck.Add(balanceCheck, st.msg.Value)
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
if blobGas := st.blobGasUsed(); blobGas > 0 {
// Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap
blobBalanceCheck := new(big.Int).SetUint64(blobGas)
blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap)
balanceCheck.Add(balanceCheck, blobBalanceCheck)
blobBalanceCheck := new(uint256.Int).SetUint64(blobGas)
if _, overflow := blobBalanceCheck.MulOverflow(blobBalanceCheck, st.msg.BlobGasFeeCap); overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
if _, overflow := balanceCheck.AddOverflow(balanceCheck, blobBalanceCheck); overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
// Pay for blobGasUsed * actual blob fee
blobFee := new(big.Int).SetUint64(blobGas)
blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee)
mgval.Add(mgval, blobFee)
blobBaseFee, overflow := uint256.FromBig(st.evm.Context.BlobBaseFee)
if overflow {
return fmt.Errorf("invalid blobBaseFee: %v", st.evm.Context.BlobBaseFee)
}
blobFee := new(uint256.Int).SetUint64(blobGas)
// In practice, overflow checking is unnecessary, as blobBaseFee cannot exceed
// BlobGasFeeCap. However, in eth_call it is still possible for users to specify
// an excessively large blob base fee and bypass the blob base fee validation.
_, overflow = blobFee.MulOverflow(blobFee, blobBaseFee)
if overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
_, overflow = mgval.AddOverflow(mgval, blobFee)
if overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
}
}
balanceCheckU256, overflow := uint256.FromBig(balanceCheck)
if overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}
if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
}
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
@ -321,8 +426,7 @@ func (st *stateTransition) buyGas() error {
st.gasRemaining = vm.NewGasBudget(st.msg.GasLimit)
st.initialBudget = st.gasRemaining.Copy()
mgvalU256, _ := uint256.FromBig(mgval)
st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
return nil
}
@ -362,21 +466,13 @@ func (st *stateTransition) preCheck() error {
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
if !skipCheck {
if l := msg.GasFeeCap.BitLen(); l > 256 {
return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
msg.From.Hex(), l)
}
if l := msg.GasTipCap.BitLen(); l > 256 {
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
msg.From.Hex(), l)
}
if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 {
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap)
}
// This will panic if baseFee is nil, but basefee presence is verified
// as part of header validation.
if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 {
if msg.GasFeeCap.CmpBig(st.evm.Context.BaseFee) < 0 {
return fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow,
msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee)
}
@ -410,7 +506,7 @@ func (st *stateTransition) preCheck() error {
if !skipCheck {
// This will panic if blobBaseFee is nil, but blobBaseFee presence
// is verified as part of header validation.
if msg.BlobGasFeeCap.Cmp(st.evm.Context.BlobBaseFee) < 0 {
if msg.BlobGasFeeCap.CmpBig(st.evm.Context.BlobBaseFee) < 0 {
return fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow,
msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee)
}
@ -462,7 +558,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
floorDataGas uint64
)
// Check clauses 4-5, subtract intrinsic gas if everything is correct
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
if err != nil {
return nil, err
}
@ -475,7 +571,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
}
// Gas limit suffices for the floor data cost (EIP-7623)
if rules.IsPrague {
floorDataGas, err = FloorDataGas(rules, msg.Data)
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
if err != nil {
return nil, err
}
@ -493,9 +589,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
}
// Check clause 6
value, overflow := uint256.FromBig(msg.Value)
if overflow {
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
value := msg.Value
if value == nil {
value = new(uint256.Int)
}
if !value.IsZero() && !st.evm.Context.CanTransfer(st.state, msg.From, value) {
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
@ -579,9 +675,12 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
}
effectiveTip := msg.GasPrice
if rules.IsLondon {
effectiveTip = new(big.Int).Sub(msg.GasPrice, st.evm.Context.BaseFee)
baseFee, overflow := uint256.FromBig(st.evm.Context.BaseFee)
if overflow {
return nil, fmt.Errorf("invalid baseFee: %v", st.evm.Context.BaseFee)
}
effectiveTip = new(uint256.Int).Sub(msg.GasPrice, baseFee)
}
effectiveTipU256, _ := uint256.FromBig(effectiveTip)
if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
// Skip fee payment when NoBaseFee is set and the fee fields
@ -589,7 +688,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// the coinbase when simulating calls.
} else {
fee := new(uint256.Int).SetUint64(st.gasUsed())
fee.Mul(fee, effectiveTipU256)
fee.Mul(fee, effectiveTip)
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
// add the coinbase to the witness iff the fee is greater than 0
@ -691,7 +790,7 @@ func (st *stateTransition) calcRefund() vm.GasBudget {
// exchanged at the original rate.
func (st *stateTransition) returnGas() {
remaining := uint256.NewInt(st.gasRemaining.RegularGas)
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
remaining.Mul(remaining, st.msg.GasPrice)
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining.RegularGas > 0 {

View file

@ -0,0 +1,287 @@
// Copyright 2026 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"bytes"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
)
func TestFloorDataGas(t *testing.T) {
addr1 := common.HexToAddress("0x1111111111111111111111111111111111111111")
addr2 := common.HexToAddress("0x2222222222222222222222222222222222222222")
key1 := common.HexToHash("0xaa")
key2 := common.HexToHash("0xbb")
tests := []struct {
name string
amsterdam bool
data []byte
accessList types.AccessList
want uint64
}{
{
name: "pre-amsterdam/empty",
want: params.TxGas,
},
{
name: "pre-amsterdam/zero-bytes-only",
data: bytes.Repeat([]byte{0x00}, 100),
// 100 zero tokens * 10 cost = 1000
want: params.TxGas + 100*params.TxCostFloorPerToken,
},
{
name: "pre-amsterdam/non-zero-bytes-only",
data: bytes.Repeat([]byte{0xff}, 100),
// 100 nz * 4 tokens * 10 cost = 4000
want: params.TxGas + 100*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken,
},
{
name: "pre-amsterdam/mixed",
data: append(bytes.Repeat([]byte{0x00}, 50), bytes.Repeat([]byte{0xff}, 50)...),
// 50 zero + 50*4 nz = 250 tokens * 10 = 2500
want: params.TxGas + (50+50*params.TxTokenPerNonZeroByte)*params.TxCostFloorPerToken,
},
{
name: "pre-amsterdam/access-list-ignored",
data: bytes.Repeat([]byte{0xff}, 10),
accessList: types.AccessList{
{Address: addr1, StorageKeys: []common.Hash{key1, key2}},
},
// pre-amsterdam: floor calculation does not include access list
want: params.TxGas + 10*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken,
},
{
name: "amsterdam/empty",
amsterdam: true,
want: params.TxGas,
},
{
name: "amsterdam/data-only",
amsterdam: true,
data: bytes.Repeat([]byte{0x00}, 1024),
// post-amsterdam: every byte = 4 tokens regardless of value
want: params.TxGas + 1024*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
},
{
name: "amsterdam/data-non-zero",
amsterdam: true,
data: bytes.Repeat([]byte{0xff}, 1024),
// same as zero data post-amsterdam
want: params.TxGas + 1024*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
},
{
name: "amsterdam/access-list-addresses-only",
amsterdam: true,
accessList: types.AccessList{
{Address: addr1},
{Address: addr2},
},
// 2 * 20 bytes * 4 tokens/byte * 16 cost/token
want: params.TxGas + 2*common.AddressLength*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
},
{
name: "amsterdam/access-list-with-storage-keys",
amsterdam: true,
accessList: types.AccessList{
{Address: addr1, StorageKeys: []common.Hash{key1, key2}},
},
// 1 addr * 20 * 4 + 2 keys * 32 * 4 = 80 + 256 = 336 tokens * 16
want: params.TxGas + (1*common.AddressLength+2*common.HashLength)*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
},
{
name: "amsterdam/mixed",
amsterdam: true,
data: bytes.Repeat([]byte{0xff}, 100),
accessList: types.AccessList{
{Address: addr1, StorageKeys: []common.Hash{key1}},
{Address: addr2, StorageKeys: []common.Hash{key1, key2}},
},
// data: 100*4 = 400; addrs: 2*20*4 = 160; keys: 3*32*4 = 384; total = 944 * 16
want: params.TxGas + (100*params.TxTokenPerNonZeroByte+2*common.AddressLength*params.TxTokenPerNonZeroByte+3*common.HashLength*params.TxTokenPerNonZeroByte)*params.TxCostFloorPerToken7976,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rules := params.Rules{IsAmsterdam: tt.amsterdam}
got, err := FloorDataGas(rules, tt.data, tt.accessList)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != tt.want {
t.Fatalf("gas mismatch: got %d, want %d", got, tt.want)
}
})
}
}
func TestIntrinsicGas(t *testing.T) {
addr1 := common.HexToAddress("0x1111111111111111111111111111111111111111")
addr2 := common.HexToAddress("0x2222222222222222222222222222222222222222")
key1 := common.HexToHash("0xaa")
key2 := common.HexToHash("0xbb")
const (
amsterdamAddressCost = uint64(common.AddressLength) * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte // 1280
amsterdamStorageKeyCost = uint64(common.HashLength) * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte // 2048
)
tests := []struct {
name string
data []byte
accessList types.AccessList
authList []types.SetCodeAuthorization
creation bool
isHomestead bool
isEIP2028 bool
isEIP3860 bool
isAmsterdam bool
want uint64
}{
{
name: "frontier/empty-call",
want: params.TxGas,
},
{
name: "frontier/contract-creation-pre-homestead",
creation: true,
isHomestead: false,
// pre-homestead, contract creation still uses TxGas
want: params.TxGas,
},
{
name: "homestead/contract-creation",
creation: true,
isHomestead: true,
want: params.TxGasContractCreation,
},
{
name: "frontier/non-zero-data",
data: bytes.Repeat([]byte{0xff}, 100),
// 100 nz bytes * 68 (frontier)
want: params.TxGas + 100*params.TxDataNonZeroGasFrontier,
},
{
name: "istanbul/non-zero-data",
data: bytes.Repeat([]byte{0xff}, 100),
isEIP2028: true,
// 100 nz bytes * 16 (post-EIP2028)
want: params.TxGas + 100*params.TxDataNonZeroGasEIP2028,
},
{
name: "istanbul/zero-data",
data: bytes.Repeat([]byte{0x00}, 100),
isEIP2028: true,
// 100 zero bytes * 4
want: params.TxGas + 100*params.TxDataZeroGas,
},
{
name: "istanbul/mixed-data",
data: append(bytes.Repeat([]byte{0x00}, 50), bytes.Repeat([]byte{0xff}, 50)...),
isEIP2028: true,
want: params.TxGas + 50*params.TxDataZeroGas + 50*params.TxDataNonZeroGasEIP2028,
},
{
name: "shanghai/init-code-word-gas",
data: bytes.Repeat([]byte{0x00}, 64), // 2 words
creation: true,
isHomestead: true,
isEIP2028: true,
isEIP3860: true,
// TxGasContractCreation + 64 zero bytes * 4 + 2 words * 2
want: params.TxGasContractCreation + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
},
{
name: "shanghai/init-code-non-multiple-of-32",
data: bytes.Repeat([]byte{0x00}, 33), // 2 words (rounded up)
creation: true,
isHomestead: true,
isEIP2028: true,
isEIP3860: true,
want: params.TxGasContractCreation + 33*params.TxDataZeroGas + 2*params.InitCodeWordGas,
},
{
name: "berlin/access-list",
accessList: types.AccessList{
{Address: addr1, StorageKeys: []common.Hash{key1, key2}},
{Address: addr2, StorageKeys: []common.Hash{key1}},
},
isEIP2028: true,
// 2 addrs * 2400 + 3 keys * 1900
want: params.TxGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas,
},
{
name: "amsterdam/access-list-extra-cost",
accessList: types.AccessList{
{Address: addr1, StorageKeys: []common.Hash{key1, key2}},
{Address: addr2, StorageKeys: []common.Hash{key1}},
},
isEIP2028: true,
isAmsterdam: true,
// base access-list charge + EIP-7981 extra
want: params.TxGas +
2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas +
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost,
},
{
name: "prague/auth-list",
authList: []types.SetCodeAuthorization{
{Address: addr1},
{Address: addr2},
{Address: addr1},
},
isEIP2028: true,
// 3 auths * 25000
want: params.TxGas + 3*params.CallNewAccountGas,
},
{
name: "amsterdam/combined",
data: bytes.Repeat([]byte{0xff}, 100),
accessList: types.AccessList{
{Address: addr1, StorageKeys: []common.Hash{key1}},
},
authList: []types.SetCodeAuthorization{
{Address: addr2},
},
isEIP2028: true,
isAmsterdam: true,
want: params.TxGas +
100*params.TxDataNonZeroGasEIP2028 +
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
1*params.CallNewAccountGas,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList,
tt.creation, tt.isHomestead, tt.isEIP2028, tt.isEIP3860, tt.isAmsterdam)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
want := vm.GasCosts{RegularGas: tt.want}
if got != want {
t.Fatalf("gas mismatch: got %+v, want %+v", got, want)
}
})
}
}

View file

@ -116,6 +116,8 @@ const (
announceThreshold = -1
)
var errLegacyTx = errors.New("legacy transaction format")
// blobTxMeta is the minimal subset of types.BlobTx necessary to validate and
// schedule the blob transactions into the following blocks. Only ever add the
// bare minimum needed fields to keep the size down (and thus number of entries
@ -147,28 +149,137 @@ type blobTxMeta struct {
evictionBlobFeeJumps float64 // Worse blob fee (converted to fee jumps) across all previous nonces
}
// newBlobTxMeta retrieves the indexed metadata fields from a blob transaction
// and assembles a helper struct to track in memory.
// Requires the transaction to have a sidecar (or that we introduce a special version tag for no-sidecar).
func newBlobTxMeta(id uint64, size uint64, storageSize uint32, tx *types.Transaction) *blobTxMeta {
if tx.BlobTxSidecar() == nil {
// This should never happen, as the pool only admits blob transactions with a sidecar
// blobTxForPool is the storage representation of a blob transaction in the
// blobpool.
type blobTxForPool struct {
Tx *types.Transaction // tx without sidecar
Version byte
Commitments []kzg4844.Commitment
Proofs []kzg4844.Proof
Blobs []kzg4844.Blob
}
// Sidecar returns BlobTxSidecar of ptx.
func (ptx *blobTxForPool) Sidecar() *types.BlobTxSidecar {
return types.NewBlobTxSidecar(ptx.Version, ptx.Blobs, ptx.Commitments, ptx.Proofs)
}
// ApplySidecar copies the sidecar's fields into the flat fields.
func (ptx *blobTxForPool) ApplySidecar(sc *types.BlobTxSidecar) {
ptx.Version = sc.Version
ptx.Commitments = sc.Commitments
ptx.Proofs = sc.Proofs
ptx.Blobs = sc.Blobs
}
// TxSize returns the transaction size on the network without
// reconstructing the transaction.
func (ptx *blobTxForPool) TxSize() uint64 {
var blobs, commitments, proofs uint64
for i := range ptx.Blobs {
blobs += rlp.BytesSize(ptx.Blobs[i][:])
}
for i := range ptx.Commitments {
commitments += rlp.BytesSize(ptx.Commitments[i][:])
}
for i := range ptx.Proofs {
proofs += rlp.BytesSize(ptx.Proofs[i][:])
}
return ptx.Tx.Size() + rlp.ListSize(rlp.ListSize(blobs)+rlp.ListSize(commitments)+rlp.ListSize(proofs))
}
// ToTx reconstructs a full Transaction with the sidecar attached.
func (ptx *blobTxForPool) ToTx() *types.Transaction {
return ptx.Tx.WithBlobTxSidecar(ptx.Sidecar())
}
// newBlobTxForPool decomposes a blob transaction into blobTxForPool type.
func newBlobTxForPool(tx *types.Transaction) *blobTxForPool {
sc := tx.BlobTxSidecar()
if sc == nil {
panic("missing blob tx sidecar")
}
return &blobTxForPool{
Tx: tx.WithoutBlobTxSidecar(),
Version: sc.Version,
Commitments: sc.Commitments,
Proofs: sc.Proofs,
Blobs: sc.Blobs,
}
}
// encodeForNetwork transforms stored blobTxForPool RLP into the standard
// network transaction encoding. This is used for getRLP.
//
// Stored RLP: [type_byte || tx_fields, version, [comms], [proofs], [blobs]]
// V0: type_byte || rlp([tx_fields, [blobs], [comms], [proofs]])
// V1: type_byte || rlp([tx_fields, version, [blobs], [comms], [proofs]])
func encodeForNetwork(storedRLP []byte) ([]byte, error) {
elems, err := rlp.SplitListValues(storedRLP)
if err != nil {
return nil, fmt.Errorf("invalid blobTxForPool RLP: %w", err)
}
if len(elems) < 5 {
return nil, fmt.Errorf("blobTxForPool has %d elements, need at least 5", len(elems))
}
// 1. Extract tx byte and other tx fields
txBytes, _, err := rlp.SplitString(elems[0])
if err != nil {
return nil, fmt.Errorf("invalid tx bytes: %w", err)
}
if len(txBytes) < 2 {
return nil, errors.New("tx bytes too short")
}
typeByte := txBytes[0]
txRLP := txBytes[1:]
// 2. Find the version of sidecar.
version, _, err := rlp.SplitUint64(elems[1])
if err != nil || version > 255 {
return nil, fmt.Errorf("invalid version: %w", err)
}
versionByte := byte(version)
// 3. Extract sidecar elements.
commitmentsRLP := elems[2]
proofsRLP := elems[3]
blobsRLP := elems[4]
// 4. Reconstruct into the network format.
var outer [][]byte
if versionByte == types.BlobSidecarVersion0 {
outer = [][]byte{txRLP, blobsRLP, commitmentsRLP, proofsRLP}
} else {
outer = [][]byte{txRLP, elems[1], blobsRLP, commitmentsRLP, proofsRLP}
}
body, err := rlp.MergeListValues(outer)
if err != nil {
return nil, err
}
// Prepend type byte and wrap as an RLP string.
inner := make([]byte, 1+len(body))
inner[0] = typeByte
copy(inner[1:], body)
return rlp.EncodeToBytes(inner)
}
// newBlobTxMeta retrieves the indexed metadata fields from a pooled blob
// transaction and assembles a helper struct to track in memory.
func newBlobTxMeta(id uint64, size uint64, storageSize uint32, ptx *blobTxForPool) *blobTxMeta {
meta := &blobTxMeta{
hash: tx.Hash(),
vhashes: tx.BlobHashes(),
version: tx.BlobTxSidecar().Version,
hash: ptx.Tx.Hash(),
vhashes: ptx.Tx.BlobHashes(),
version: ptx.Version,
id: id,
storageSize: storageSize,
size: size,
nonce: tx.Nonce(),
costCap: uint256.MustFromBig(tx.Cost()),
execTipCap: uint256.MustFromBig(tx.GasTipCap()),
execFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
blobFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap()),
execGas: tx.Gas(),
blobGas: tx.BlobGas(),
nonce: ptx.Tx.Nonce(),
costCap: uint256.MustFromBig(ptx.Tx.Cost()),
execTipCap: uint256.MustFromBig(ptx.Tx.GasTipCap()),
execFeeCap: uint256.MustFromBig(ptx.Tx.GasFeeCap()),
blobFeeCap: uint256.MustFromBig(ptx.Tx.BlobGasFeeCap()),
execGas: ptx.Tx.Gas(),
blobGas: ptx.Tx.BlobGas(),
}
meta.basefeeJumps = dynamicFeeJumps(meta.execFeeCap)
meta.blobfeeJumps = dynamicBlobFeeJumps(meta.blobFeeCap)
@ -460,10 +571,17 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
return err
}
// Index all transactions on disk and delete anything unprocessable
var fails []uint64
var (
toDelete []uint64
convertTxs []uint64
)
index := func(id uint64, size uint32, blob []byte) {
if p.parseTransaction(id, size, blob) != nil {
fails = append(fails, id)
err := p.parseTransaction(id, size, blob)
if err != nil {
toDelete = append(toDelete, id)
}
if errors.Is(err, errLegacyTx) {
convertTxs = append(convertTxs, id)
}
}
store, err := billy.Open(billy.Options{Path: queuedir, Repair: true}, slotter, index)
@ -472,17 +590,58 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
}
p.store = store
if len(fails) > 0 {
log.Warn("Dropping invalidated blob transactions", "ids", fails)
dropInvalidMeter.Mark(int64(len(fails)))
// Migrate legacy transactions (types.Transaction) to pooledBlobTx format.
if len(convertTxs) > 0 {
for _, id := range convertTxs {
var tx types.Transaction
data, err := p.store.Get(id)
if err != nil {
continue
}
err = rlp.DecodeBytes(data, &tx)
if err != nil {
continue
}
if tx.BlobTxSidecar() == nil {
continue
}
ptx := newBlobTxForPool(&tx)
blob, err := rlp.EncodeToBytes(ptx)
if err != nil {
continue
}
id, err := p.store.Put(blob)
if err != nil {
continue
}
meta := newBlobTxMeta(id, ptx.TxSize(), p.store.Size(id), ptx)
for _, id := range fails {
// If the newly inserted transaction fails to be tracked,
// it should also be removed with those in `toDelete`
sender, err := types.Sender(p.signer, ptx.Tx)
if err != nil {
toDelete = append(toDelete, id)
continue
}
if err := p.trackTransaction(meta, sender); err != nil {
toDelete = append(toDelete, id)
continue
}
}
}
if len(toDelete) > 0 {
log.Warn("Dropping invalidated blob transactions", "ids", toDelete)
dropInvalidMeter.Mark(int64(len(toDelete)))
for _, id := range toDelete {
if err := p.store.Delete(id); err != nil {
p.Close()
return err
}
}
}
// Sort the indexed transactions by nonce and delete anything gapped, create
// the eviction heap of anyone still standing
for addr := range p.index {
@ -558,36 +717,33 @@ func (p *BlobPool) Close() error {
// parseTransaction is a callback method on pool creation that gets called for
// each transaction on disk to create the in-memory metadata index.
// Announced state is not initialized here, it needs to be iniitalized seprately.
// Return value `bool` is set to true when the entry has old Transaction type.
func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
tx := new(types.Transaction)
if err := rlp.DecodeBytes(blob, tx); err != nil {
// This path is impossible unless the disk data representation changes
// across restarts. For that ever improbable case, recover gracefully
// by ignoring this data entry.
log.Error("Failed to decode blob pool entry", "id", id, "err", err)
var ptx blobTxForPool
if err := rlp.DecodeBytes(blob, &ptx); err != nil {
kind, content, _, splitErr := rlp.Split(blob)
// check whether it is legacy tx type
if splitErr == nil && kind == rlp.String && len(content) > 1 && content[0] == 3 {
return errLegacyTx
}
return err
}
if tx.BlobTxSidecar() == nil {
log.Error("Missing sidecar in blob pool entry", "id", id, "hash", tx.Hash())
return errors.New("missing blob sidecar")
meta := newBlobTxMeta(id, ptx.TxSize(), size, &ptx)
sender, err := types.Sender(p.signer, ptx.Tx)
if err != nil {
return err
}
return p.trackTransaction(meta, sender)
}
meta := newBlobTxMeta(id, tx.Size(), size, tx)
// trackTransaction registers a transaction's metadata in the pool's indices.
func (p *BlobPool) trackTransaction(meta *blobTxMeta, sender common.Address) error {
if p.lookup.exists(meta.hash) {
// This path is only possible after a crash, where deleted items are not
// removed via the normal shutdown-startup procedure and thus may get
// partially resurrected.
log.Error("Rejecting duplicate blob pool entry", "id", id, "hash", tx.Hash())
return errors.New("duplicate blob entry")
}
sender, err := types.Sender(p.signer, tx)
if err != nil {
// This path is impossible unless the signature validity changes across
// restarts. For that ever improbable case, recover gracefully by ignoring
// this data entry.
log.Error("Failed to recover blob tx sender", "id", id, "hash", tx.Hash(), "err", err)
return err
log.Error("Rejecting duplicate blob pool entry", "id", meta.id, "hash", meta.hash)
return fmt.Errorf("duplicate blob entry %d, %s", meta.id, meta.hash)
}
if _, ok := p.index[sender]; !ok {
if err := p.reserver.Hold(sender); err != nil {
@ -863,17 +1019,17 @@ func (p *BlobPool) offload(addr common.Address, nonce uint64, id uint64, inclusi
log.Error("Blobs missing for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
return
}
var tx types.Transaction
if err = rlp.DecodeBytes(data, &tx); err != nil {
var ptx blobTxForPool
if err := rlp.DecodeBytes(data, &ptx); err != nil {
log.Error("Blobs corrupted for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
return
}
block, ok := inclusions[tx.Hash()]
block, ok := inclusions[ptx.Tx.Hash()]
if !ok {
log.Warn("Blob transaction swapped out by signer", "from", addr, "nonce", nonce, "id", id)
return
}
if err := p.limbo.push(&tx, block); err != nil {
if err := p.limbo.push(&ptx, block); err != nil {
log.Warn("Failed to offload blob tx into limbo", "err", err)
return
}
@ -1108,7 +1264,7 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
// Retrieve the associated blob from the limbo. Without the blobs, we cannot
// add the transaction back into the pool as it is not mineable.
tx, err := p.limbo.pull(txhash)
ptx, err := p.limbo.pull(txhash)
if err != nil {
log.Error("Blobs unavailable, dropping reorged tx", "err", err)
return err
@ -1124,30 +1280,29 @@ func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
// could theoretically halt a Geth node for ~1.2s by reorging per block. However,
// this attack is financially inefficient to execute.
head := p.head.Load()
if p.chain.Config().IsOsaka(head.Number, head.Time) && tx.BlobTxSidecar().Version == types.BlobSidecarVersion0 {
if err := tx.BlobTxSidecar().ToV1(); err != nil {
if p.chain.Config().IsOsaka(head.Number, head.Time) && ptx.Version == types.BlobSidecarVersion0 {
sc := ptx.Sidecar()
if err := sc.ToV1(); err != nil {
log.Error("Failed to convert the legacy sidecar", "err", err)
return err
}
log.Info("Legacy blob transaction is reorged", "hash", tx.Hash())
ptx.ApplySidecar(sc)
log.Info("Legacy blob transaction is reorged", "hash", ptx.Tx.Hash())
}
// Serialize the transaction back into the primary datastore.
blob, err := rlp.EncodeToBytes(tx)
blob, err := rlp.EncodeToBytes(ptx)
if err != nil {
log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err)
log.Error("Failed to encode transaction for storage", "hash", ptx.Tx.Hash(), "err", err)
return err
}
id, err := p.store.Put(blob)
if err != nil {
log.Error("Failed to write transaction into storage", "hash", tx.Hash(), "err", err)
log.Error("Failed to write transaction into storage", "hash", ptx.Tx.Hash(), "err", err)
return err
}
// Update the indices and metrics
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), tx)
meta := newBlobTxMeta(id, ptx.TxSize(), p.store.Size(id), ptx)
if _, ok := p.index[addr]; !ok {
if err := p.reserver.Hold(addr); err != nil {
log.Warn("Failed to reserve account for blob pool", "tx", tx.Hash(), "from", addr, "err", err)
log.Warn("Failed to reserve account for blob pool", "tx", ptx.Tx.Hash(), "from", addr, "err", err)
return err
}
p.index[addr] = []*blobTxMeta{meta}
@ -1404,20 +1559,29 @@ func (p *BlobPool) Get(hash common.Hash) *types.Transaction {
if len(data) == 0 {
return nil
}
item := new(types.Transaction)
if err := rlp.DecodeBytes(data, item); err != nil {
var ptx blobTxForPool
if err := rlp.DecodeBytes(data, &ptx); err != nil {
id, _ := p.lookup.storeidOfTx(hash)
log.Error("Blobs corrupted for traced transaction",
"hash", hash, "id", id, "err", err)
return nil
}
return item
return ptx.ToTx()
}
// GetRLP returns a RLP-encoded transaction if it is contained in the pool.
// GetRLP returns a RLP-encoded transaction for network if it is contained in the pool.
// It converts the pool's internal type to the RLP format used by the eth protocol:
// e.g. type_byte || [..., version, [blobs], [comms], [proofs]]
func (p *BlobPool) GetRLP(hash common.Hash) []byte {
return p.getRLP(hash)
data := p.getRLP(hash)
rlp, err := encodeForNetwork(data)
if err != nil {
log.Error("Failed to encode pooled tx into the network type", "hash", hash, "err", err)
return nil
}
return rlp
}
// GetMetadata returns the transaction type and transaction size with the
@ -1486,18 +1650,14 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo
}
// Decode the blob transaction
tx := new(types.Transaction)
if err := rlp.DecodeBytes(data, tx); err != nil {
var ptx blobTxForPool
if err := rlp.DecodeBytes(data, &ptx); err != nil {
log.Error("Blobs corrupted for traced transaction", "id", txID, "err", err)
continue
}
sidecar := tx.BlobTxSidecar()
if sidecar == nil {
log.Error("Blob tx without sidecar", "hash", tx.Hash(), "id", txID)
continue
}
sidecar := ptx.Sidecar()
// Traverse the blobs in the transaction
for i, hash := range tx.BlobHashes() {
for i, hash := range ptx.Tx.BlobHashes() {
list, ok := indices[hash]
if !ok {
continue // non-interesting blob
@ -1517,7 +1677,8 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo
case types.BlobSidecarVersion1:
cellProofs, err := sidecar.CellProofsAt(i)
if err != nil {
return nil, nil, nil, err
log.Error("Failed to get cell proofs", "id", txID, "err", err)
continue
}
pf = cellProofs
}
@ -1596,9 +1757,10 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
// Store the tx in memory, and revalidate later
from, _ := types.Sender(p.signer, tx)
allowance := p.gappedAllowance(from)
if allowance >= 1 && len(p.gapped) < maxGapped {
if allowance >= 1 && len(p.gappedSource) < maxGapped {
p.gapped[from] = append(p.gapped[from], tx)
p.gappedSource[tx.Hash()] = from
gappedGauge.Update(int64(len(p.gappedSource)))
log.Trace("added tx to gapped blob queue", "allowance", allowance, "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "qlen", len(p.gapped[from]))
return nil
} else {
@ -1606,6 +1768,7 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
// transactions by keeping the old and dropping this one.
// Thus replacing a gapped transaction with another gapped transaction
// is discouraged.
addGappedFullMeter.Mark(1)
log.Trace("no gapped blob queue allowance", "allowance", allowance, "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "qlen", len(p.gapped[from]))
}
case errors.Is(err, core.ErrInsufficientFunds):
@ -1641,7 +1804,8 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
}
// Transaction permitted into the pool from a nonce and cost perspective,
// insert it into the database and update the indices
blob, err := rlp.EncodeToBytes(tx)
ptx := newBlobTxForPool(tx)
blob, err := rlp.EncodeToBytes(ptx)
if err != nil {
log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err)
return err
@ -1650,7 +1814,7 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
if err != nil {
return err
}
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), tx)
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), ptx)
var (
next = p.state.GetNonce(from)
@ -1791,6 +1955,7 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
// We do not recurse here, but continue to loop instead.
// We are under lock, so we can add the transaction directly.
if err := p.addLocked(tx, false); err == nil {
gappedPromotedMeter.Mark(1)
log.Trace("Gapped blob transaction added to pool", "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "qlen", len(p.gapped[from]))
} else {
log.Trace("Gapped blob transaction not accepted", "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "err", err)
@ -1802,6 +1967,7 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
} else {
p.gapped[from] = gtxs
}
gappedGauge.Update(int64(len(p.gappedSource)))
}
return nil
}
@ -2069,8 +2235,9 @@ func (p *BlobPool) evictGapped() {
keep = append(keep, gtx)
}
}
if len(keep) < len(txs) {
log.Trace("Evicting old gapped blob transactions", "count", len(txs)-len(keep), "from", from)
if evicted := len(txs) - len(keep); evicted > 0 {
gappedEvictedMeter.Mark(int64(evicted))
log.Trace("Evicting old gapped blob transactions", "count", evicted, "from", from)
}
if len(keep) == 0 {
delete(p.gapped, from)
@ -2078,6 +2245,7 @@ func (p *BlobPool) evictGapped() {
p.gapped[from] = keep
}
}
gappedGauge.Update(int64(len(p.gappedSource)))
}
// isAnnouncable checks whether a transaction is announcable based on its

View file

@ -235,6 +235,12 @@ func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64,
return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx)
}
// encodeForPool encodes a blob transaction in the blobTxForPool storage format.
func encodeForPool(tx *types.Transaction) []byte {
blob, _ := rlp.EncodeToBytes(newBlobTxForPool(tx))
return blob
}
// makeMultiBlobTx is a utility method to construct a ramdom blob tx with
// certain number of blobs in its sidecar.
func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, blobCount int, blobOffset int, key *ecdsa.PrivateKey, version byte) *types.Transaction {
@ -530,7 +536,7 @@ func TestOpenDrops(t *testing.T) {
)
for _, nonce := range []uint64{0, 1, 3, 4, 6, 7} { // first gap at #2, another at #5
tx := makeTx(nonce, 1, 1, 1, gapper)
blob, _ := rlp.EncodeToBytes(tx)
blob := encodeForPool(tx)
id, _ := store.Put(blob)
if nonce < 2 {
@ -547,7 +553,7 @@ func TestOpenDrops(t *testing.T) {
)
for _, nonce := range []uint64{1, 2, 3} { // first gap at #0, all set dangling
tx := makeTx(nonce, 1, 1, 1, dangler)
blob, _ := rlp.EncodeToBytes(tx)
blob := encodeForPool(tx)
id, _ := store.Put(blob)
dangling[id] = struct{}{}
@ -560,7 +566,7 @@ func TestOpenDrops(t *testing.T) {
)
for _, nonce := range []uint64{0, 1, 2} { // account nonce at 3, all set filled
tx := makeTx(nonce, 1, 1, 1, filler)
blob, _ := rlp.EncodeToBytes(tx)
blob := encodeForPool(tx)
id, _ := store.Put(blob)
filled[id] = struct{}{}
@ -573,7 +579,7 @@ func TestOpenDrops(t *testing.T) {
)
for _, nonce := range []uint64{0, 1, 2, 3} { // account nonce at 2, half filled
tx := makeTx(nonce, 1, 1, 1, overlapper)
blob, _ := rlp.EncodeToBytes(tx)
blob := encodeForPool(tx)
id, _ := store.Put(blob)
if nonce >= 2 {
@ -595,7 +601,7 @@ func TestOpenDrops(t *testing.T) {
} else {
tx = makeTx(uint64(i), 1, 1, 1, underpayer)
}
blob, _ := rlp.EncodeToBytes(tx)
blob := encodeForPool(tx)
id, _ := store.Put(blob)
underpaid[id] = struct{}{}
@ -614,7 +620,7 @@ func TestOpenDrops(t *testing.T) {
} else {
tx = makeTx(uint64(i), 1, 1, 1, outpricer)
}
blob, _ := rlp.EncodeToBytes(tx)
blob := encodeForPool(tx)
id, _ := store.Put(blob)
if i < 2 {
@ -636,7 +642,7 @@ func TestOpenDrops(t *testing.T) {
} else {
tx = makeTx(nonce, 1, 1, 1, exceeder)
}
blob, _ := rlp.EncodeToBytes(tx)
blob := encodeForPool(tx)
id, _ := store.Put(blob)
exceeded[id] = struct{}{}
@ -654,7 +660,7 @@ func TestOpenDrops(t *testing.T) {
} else {
tx = makeTx(nonce, 1, 1, 1, overdrafter)
}
blob, _ := rlp.EncodeToBytes(tx)
blob := encodeForPool(tx)
id, _ := store.Put(blob)
if nonce < 1 {
@ -670,7 +676,7 @@ func TestOpenDrops(t *testing.T) {
overcapped = make(map[uint64]struct{})
)
for nonce := uint64(0); nonce < maxTxsPerAccount+3; nonce++ {
blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, overcapper))
blob := encodeForPool(makeTx(nonce, 1, 1, 1, overcapper))
id, _ := store.Put(blob)
if nonce < maxTxsPerAccount {
@ -686,7 +692,7 @@ func TestOpenDrops(t *testing.T) {
duplicated = make(map[uint64]struct{})
)
for _, nonce := range []uint64{0, 1, 2} {
blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, duplicater))
blob := encodeForPool(makeTx(nonce, 1, 1, 1, duplicater))
for i := 0; i < int(nonce)+1; i++ {
id, _ := store.Put(blob)
@ -705,7 +711,7 @@ func TestOpenDrops(t *testing.T) {
)
for _, nonce := range []uint64{0, 1, 2} {
for i := 0; i < int(nonce)+1; i++ {
blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, uint64(i)+1 /* unique hashes */, 1, repeater))
blob := encodeForPool(makeTx(nonce, 1, uint64(i)+1 /* unique hashes */, 1, repeater))
id, _ := store.Put(blob)
if i == 0 {
@ -842,7 +848,7 @@ func TestOpenIndex(t *testing.T) {
)
for _, i := range []int{5, 3, 4, 2, 0, 1} { // Randomize the tx insertion order to force sorting on load
tx := makeTx(uint64(i), txExecTipCaps[i], txExecFeeCaps[i], txBlobFeeCaps[i], key)
blob, _ := rlp.EncodeToBytes(tx)
blob := encodeForPool(tx)
store.Put(blob)
}
store.Close()
@ -934,9 +940,9 @@ func TestOpenHeap(t *testing.T) {
tx2 = makeTx(0, 1, 800, 70, key2)
tx3 = makeTx(0, 1, 1500, 110, key3)
blob1, _ = rlp.EncodeToBytes(tx1)
blob2, _ = rlp.EncodeToBytes(tx2)
blob3, _ = rlp.EncodeToBytes(tx3)
blob1 = encodeForPool(tx1)
blob2 = encodeForPool(tx2)
blob3 = encodeForPool(tx3)
heapOrder = []common.Address{addr2, addr1, addr3}
heapIndex = map[common.Address]int{addr2: 0, addr1: 1, addr3: 2}
@ -1009,9 +1015,9 @@ func TestOpenCap(t *testing.T) {
tx2 = makeTx(0, 1, 800, 70, key2)
tx3 = makeTx(0, 1, 1500, 110, key3)
blob1, _ = rlp.EncodeToBytes(tx1)
blob2, _ = rlp.EncodeToBytes(tx2)
blob3, _ = rlp.EncodeToBytes(tx3)
blob1 = encodeForPool(tx1)
blob2 = encodeForPool(tx2)
blob3 = encodeForPool(tx3)
keep = []common.Address{addr1, addr3}
drop = []common.Address{addr2}
@ -1098,8 +1104,8 @@ func TestChangingSlotterSize(t *testing.T) {
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 0, key2, types.BlobSidecarVersion0)
tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3, types.BlobSidecarVersion0)
blob1, _ = rlp.EncodeToBytes(tx1)
blob2, _ = rlp.EncodeToBytes(tx2)
blob1 = encodeForPool(tx1)
blob2 = encodeForPool(tx2)
)
// Write the two safely sized txs to store. note: although the store is
@ -1201,8 +1207,8 @@ func TestBillyMigration(t *testing.T) {
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 0, key2, types.BlobSidecarVersion0)
tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3, types.BlobSidecarVersion0)
blob1, _ = rlp.EncodeToBytes(tx1)
blob2, _ = rlp.EncodeToBytes(tx2)
blob1 = encodeForPool(tx1)
blob2 = encodeForPool(tx2)
)
// Write the two safely sized txs to store. note: although the store is
@ -1281,6 +1287,85 @@ func TestBillyMigration(t *testing.T) {
}
}
// TestLegacyTxConversion verifies that on Init, transactions stored in the
// legacy *types.Transaction RLP format are detected and migrated into the new
// blobTxForPool storage format, and that they remain retrievable via the pool
// API after the conversion.
func TestLegacyTxConversion(t *testing.T) {
storage := t.TempDir()
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
os.MkdirAll(filepath.Join(storage, limboedTransactionStore), 0700)
// Initialize the pending store with two blob transactions encoded in the
// legacy format.
queuedir := filepath.Join(storage, pendingTransactionStore)
store, err := billy.Open(billy.Options{Path: queuedir}, newSlotter(testMaxBlobsPerBlock), nil)
if err != nil {
t.Fatalf("failed to open billy: %v", err)
}
key1, _ := crypto.GenerateKey()
key2, _ := crypto.GenerateKey()
addr1 := crypto.PubkeyToAddress(key1.PublicKey)
addr2 := crypto.PubkeyToAddress(key2.PublicKey)
tx1 := makeMultiBlobTx(0, 1, 1000, 100, 2, 0, key1, types.BlobSidecarVersion0)
tx2 := makeMultiBlobTx(0, 1, 1000, 100, 2, 2, key2, types.BlobSidecarVersion0)
for _, tx := range []*types.Transaction{tx1, tx2} {
legacy, err := rlp.EncodeToBytes(tx)
if err != nil {
t.Fatalf("failed to legacy-encode tx: %v", err)
}
if _, err := store.Put(legacy); err != nil {
t.Fatalf("failed to put legacy blob: %v", err)
}
}
store.Close()
// Init should migrate the legacy entries into the new storage format.
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)
chain := &testBlockChain{
config: params.MainnetChainConfig,
basefee: uint256.NewInt(params.InitialBaseFee),
blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice),
statedb: statedb,
}
pool := New(Config{Datadir: storage}, chain, nil)
if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil {
t.Fatalf("failed to create blob pool: %v", err)
}
defer pool.Close()
// Both transactions should be retrievable.
for _, want := range []*types.Transaction{tx1, tx2} {
got := pool.Get(want.Hash())
if got == nil {
t.Fatalf("migrated tx %s not found in pool", want.Hash())
}
if got.BlobTxSidecar() == nil {
t.Fatalf("migrated tx %s lost its sidecar", want.Hash())
}
if got.Hash() != want.Hash() {
t.Fatalf("migrated tx hash mismatch: have %s, want %s", got.Hash(), want.Hash())
}
}
// Legacy formats should not exist on pool.store
pool.store.Iterate(func(id uint64, size uint32, blob []byte) {
var ptx blobTxForPool
if err := rlp.DecodeBytes(blob, &ptx); err != nil {
t.Errorf("entry %d not in new blobTxForPool format: %v", id, err)
}
})
verifyPoolInternals(t, pool)
}
// TestBlobCountLimit tests the blobpool enforced limits on the max blob count.
func TestBlobCountLimit(t *testing.T) {
var (
@ -1746,7 +1831,7 @@ func TestAdd(t *testing.T) {
// Sign the seed transactions and store them in the data store
for _, tx := range seed.txs {
signed := types.MustSignNewTx(keys[acc], types.LatestSigner(params.MainnetChainConfig), tx)
blob, _ := rlp.EncodeToBytes(signed)
blob := encodeForPool(signed)
store.Put(blob)
}
}
@ -1853,9 +1938,9 @@ func TestGetBlobs(t *testing.T) {
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 6, key2, types.BlobSidecarVersion1) // [6, 12)
tx3 = makeMultiBlobTx(0, 1, 800, 110, 6, 12, key3, types.BlobSidecarVersion0) // [12, 18)
blob1, _ = rlp.EncodeToBytes(tx1)
blob2, _ = rlp.EncodeToBytes(tx2)
blob3, _ = rlp.EncodeToBytes(tx3)
blob1 = encodeForPool(tx1)
blob2 = encodeForPool(tx2)
blob3 = encodeForPool(tx3)
)
// Write the two safely sized txs to store. note: although the store is
@ -2055,6 +2140,32 @@ func TestGetBlobs(t *testing.T) {
pool.Close()
}
// TestEncodeForNetwork verifies that encodeForNetwork produces output identical
// to rlp.EncodeToBytes on the original transaction, for both V0 and V1 sidecars.
func TestEncodeForNetwork(t *testing.T) {
t.Run("v0", func(t *testing.T) { testEncodeForNetwork(t, types.BlobSidecarVersion0) })
t.Run("v1", func(t *testing.T) { testEncodeForNetwork(t, types.BlobSidecarVersion1) })
}
func testEncodeForNetwork(t *testing.T, version byte) {
key, _ := crypto.GenerateKey()
tx := makeMultiBlobTx(0, 1, 1, 1, 1, 0, key, version)
wantRLP, err := rlp.EncodeToBytes(tx)
if err != nil {
t.Fatalf("failed to encode tx: %v", err)
}
storedRLP := encodeForPool(tx)
gotRLP, err := encodeForNetwork(storedRLP)
if err != nil {
t.Fatalf("encodeForNetwork failed: %v", err)
}
if !bytes.Equal(gotRLP, wantRLP) {
t.Fatalf("network encoding mismatch (version %d): got %d bytes, want %d bytes", version, len(gotRLP), len(wantRLP))
}
}
// fakeBilly is a billy.Database implementation which just drops data on the floor.
type fakeBilly struct {
billy.Database

View file

@ -33,7 +33,7 @@ import (
type limboBlob struct {
TxHash common.Hash // Owner transaction's hash to support resurrecting reorged txs
Block uint64 // Block in which the blob transaction was included
Tx *types.Transaction
Ptx *blobTxForPool
}
// limbo is a light, indexed database to temporarily store recently included
@ -146,15 +146,14 @@ func (l *limbo) finalize(final *types.Header) {
// push stores a new blob transaction into the limbo, waiting until finality for
// it to be automatically evicted.
func (l *limbo) push(tx *types.Transaction, block uint64) error {
// If the blobs are already tracked by the limbo, consider it a programming
// error. There's not much to do against it, but be loud.
if _, ok := l.index[tx.Hash()]; ok {
log.Error("Limbo cannot push already tracked blobs", "tx", tx.Hash())
func (l *limbo) push(ptx *blobTxForPool, block uint64) error {
hash := ptx.Tx.Hash()
if _, ok := l.index[hash]; ok {
log.Error("Limbo cannot push already tracked blobs", "tx", hash)
return errors.New("already tracked blob transaction")
}
if err := l.setAndIndex(tx, block); err != nil {
log.Error("Failed to set and index limboed blobs", "tx", tx.Hash(), "err", err)
if err := l.setAndIndex(ptx, block); err != nil {
log.Error("Failed to set and index limboed blobs", "tx", hash, "err", err)
return err
}
return nil
@ -163,7 +162,7 @@ func (l *limbo) push(tx *types.Transaction, block uint64) error {
// pull retrieves a previously pushed set of blobs back from the limbo, removing
// it at the same time. This method should be used when a previously included blob
// transaction gets reorged out.
func (l *limbo) pull(tx common.Hash) (*types.Transaction, error) {
func (l *limbo) pull(tx common.Hash) (*blobTxForPool, error) {
// If the blobs are not tracked by the limbo, there's not much to do. This
// can happen for example if a blob transaction is mined without pushing it
// into the network first.
@ -177,7 +176,7 @@ func (l *limbo) pull(tx common.Hash) (*types.Transaction, error) {
log.Error("Failed to get and drop limboed blobs", "tx", tx, "id", id, "err", err)
return nil, err
}
return item.Tx, nil
return item.Ptx, nil
}
// update changes the block number under which a blob transaction is tracked. This
@ -209,7 +208,7 @@ func (l *limbo) update(txhash common.Hash, block uint64) {
log.Error("Failed to get and drop limboed blobs", "tx", txhash, "id", id, "err", err)
return
}
if err := l.setAndIndex(item.Tx, block); err != nil {
if err := l.setAndIndex(item.Ptx, block); err != nil {
log.Error("Failed to set and index limboed blobs", "tx", txhash, "err", err)
return
}
@ -240,12 +239,12 @@ func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) {
// setAndIndex assembles a limbo blob database entry and stores it, also updating
// the in-memory indices.
func (l *limbo) setAndIndex(tx *types.Transaction, block uint64) error {
txhash := tx.Hash()
func (l *limbo) setAndIndex(ptx *blobTxForPool, block uint64) error {
txhash := ptx.Tx.Hash()
item := &limboBlob{
TxHash: txhash,
Block: block,
Tx: tx,
Ptx: ptx,
}
data, err := rlp.EncodeToBytes(item)
if err != nil {

View file

@ -97,9 +97,15 @@ var (
addUnderpricedMeter = metrics.NewRegisteredMeter("blobpool/add/underpriced", nil) // Gas tip too low, neutral
addStaleMeter = metrics.NewRegisteredMeter("blobpool/add/stale", nil) // Nonce already filled, reject, bad-ish
addGappedMeter = metrics.NewRegisteredMeter("blobpool/add/gapped", nil) // Nonce gapped, reject, bad-ish
addGappedFullMeter = metrics.NewRegisteredMeter("blobpool/add/gappedfull", nil) // Gapped queue full, reject, neutral
addOverdraftedMeter = metrics.NewRegisteredMeter("blobpool/add/overdrafted", nil) // Balance exceeded, reject, neutral
addOvercappedMeter = metrics.NewRegisteredMeter("blobpool/add/overcapped", nil) // Per-account cap exceeded, reject, neutral
addNoreplaceMeter = metrics.NewRegisteredMeter("blobpool/add/noreplace", nil) // Replacement fees or tips too low, neutral
addNonExclusiveMeter = metrics.NewRegisteredMeter("blobpool/add/nonexclusive", nil) // Plain transaction from same account exists, reject, neutral
addValidMeter = metrics.NewRegisteredMeter("blobpool/add/valid", nil) // Valid transaction, add, neutral
// Gapped queue metrics for observability
gappedGauge = metrics.NewRegisteredGauge("blobpool/gapped/count", nil) // Current gapped queue size
gappedPromotedMeter = metrics.NewRegisteredMeter("blobpool/gapped/promoted", nil) // Gapped txs successfully promoted to pool
gappedEvictedMeter = metrics.NewRegisteredMeter("blobpool/gapped/evicted", nil) // Gapped txs evicted due to timeout/stale
)

View file

@ -467,8 +467,8 @@ func (pool *LegacyPool) stats() (int, int) {
// Content retrieves the data content of the transaction pool, returning all the
// pending as well as queued transactions, grouped by account and sorted by nonce.
func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
pool.mu.Lock()
defer pool.mu.Unlock()
pool.mu.RLock()
defer pool.mu.RUnlock()
pending := make(map[common.Address][]*types.Transaction, len(pool.pending))
for addr, list := range pool.pending {
@ -503,8 +503,8 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) (map[common.Address
if filter.BlobTxs {
return nil, 0
}
pool.mu.Lock()
defer pool.mu.Unlock()
pool.mu.RLock()
defer pool.mu.RUnlock()
var count int
pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))

View file

@ -18,6 +18,7 @@
package locals
import (
"cmp"
"slices"
"sync"
"time"
@ -151,7 +152,7 @@ func (tracker *TxTracker) recheck(journalCheck bool) []*types.Transaction {
for _, list := range rejournal {
// cmp(a, b) should return a negative number when a < b,
slices.SortFunc(list, func(a, b *types.Transaction) int {
return int(a.Nonce() - b.Nonce())
return cmp.Compare(a.Nonce(), b.Nonce())
})
}
// Rejournal the tracker while holding the lock. No new transactions will

View file

@ -125,7 +125,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
}
// Ensure the transaction has more gas than the bare minimum needed to cover
// the transaction metadata
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai)
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
if err != nil {
return err
}
@ -134,7 +134,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
}
// Ensure the transaction can cover floor data gas.
if rules.IsPrague {
floorDataGas, err := core.FloorDataGas(rules, tx.Data())
floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
if err != nil {
return err
}

View file

@ -103,7 +103,7 @@ func Sign(hash []byte, prv *ecdsa.PrivateKey) ([]byte, error) {
// The public key should be in compressed (33 bytes) or uncompressed (65 bytes) format.
// The signature should have the 64 byte [R || S] format.
func VerifySignature(pubkey, hash, signature []byte) bool {
if len(signature) != 64 {
if len(signature) != 64 || len(hash) != DigestLength {
return false
}
var r, s secp256k1.ModNScalar

View file

@ -49,7 +49,6 @@ import (
"github.com/ethereum/go-ethereum/eth/protocols/snap"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/internal/shutdowncheck"
"github.com/ethereum/go-ethereum/internal/version"
@ -105,7 +104,6 @@ type Ethereum struct {
// DB interfaces
chainDb ethdb.Database // Block chain database
eventMux *event.TypeMux
engine consensus.Engine
accountManager *accounts.Manager
@ -194,7 +192,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
eth := &Ethereum{
config: config,
chainDb: chainDb,
eventMux: stack.EventMux(),
accountManager: stack.AccountManager(),
engine: engine,
networkID: networkID,
@ -237,6 +234,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
StateHistory: config.StateHistory,
TrienodeHistory: config.TrienodeHistory,
NodeFullValueCheckpoint: config.NodeFullValueCheckpoint,
BinTrieGroupDepth: config.BinTrieGroupDepth,
StateScheme: scheme,
HistoryPolicy: histPolicy,
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
@ -343,7 +341,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
Network: networkID,
Sync: config.SyncMode,
BloomCache: uint64(cacheLimit),
EventMux: eth.eventMux,
RequiredBlocks: config.RequiredBlocks,
}); err != nil {
return nil, err
@ -404,7 +401,7 @@ func (s *Ethereum) APIs() []rpc.API {
Service: NewMinerAPI(s),
}, {
Namespace: "eth",
Service: downloader.NewDownloaderAPI(s.handler.downloader, s.blockchain, s.eventMux),
Service: downloader.NewDownloaderAPI(s.handler.downloader, s.blockchain),
}, {
Namespace: "admin",
Service: NewAdminAPI(s),
@ -599,7 +596,6 @@ func (s *Ethereum) Stop() error {
s.shutdownTracker.Stop()
s.chainDb.Close()
s.eventMux.Stop()
return nil
}

View file

@ -82,6 +82,9 @@ const (
// beaconUpdateWarnFrequency is the frequency at which to warn the user that
// the beacon client is offline.
beaconUpdateWarnFrequency = 5 * time.Minute
// maxReorgDepth is the maximum reorg depth accepted via forkchoiceUpdated.
maxReorgDepth = 32
)
type ConsensusAPI struct {
@ -237,6 +240,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV4(ctx context.Context, update engine.
func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (result engine.ForkChoiceResponse, err error) {
ctx, _, spanEnd := telemetry.StartSpan(ctx, "engine.forkchoiceUpdated")
defer spanEnd(&err)
api.forkchoiceLock.Lock()
defer api.forkchoiceLock.Unlock()
@ -321,10 +325,23 @@ func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.Fo
// generating the payload. It's a special corner case that a few slots are
// missing and we are requested to generate the payload in slot.
} else {
// If the head block is already in our canonical chain, the beacon client is
// probably resyncing. Ignore the update.
log.Info("Ignoring beacon update to old head", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().Number)
return valid(nil), nil
if finalized := api.eth.BlockChain().CurrentFinalBlock(); finalized != nil && block.NumberU64() <= finalized.Number.Uint64() {
log.Info("Skipping beacon update to finalized ancestor", "number", block.NumberU64(), "hash", update.HeadBlockHash)
return valid(nil), nil
}
depth := api.eth.BlockChain().CurrentBlock().Number.Uint64() - block.NumberU64()
if depth >= maxReorgDepth {
log.Warn("Refusing too deep reorg", "depth", depth, "head", update.HeadBlockHash)
return engine.STATUS_INVALID, engine.TooDeepReorg.With(fmt.Errorf("reorg depth %d exceeds limit %d", depth, maxReorgDepth))
}
if !api.eth.Synced() {
log.Info("Ignoring beacon update to old head while syncing", "number", block.NumberU64(), "hash", update.HeadBlockHash)
return valid(nil), nil
}
if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
log.Error("Error setting canonical", "number", block.NumberU64(), "hash", update.HeadBlockHash, "error", err)
return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err
}
}
api.eth.SetSynced()
@ -629,6 +646,7 @@ func (api *ConsensusAPI) getBlobs(hashes []common.Hash, v2 bool) ([]*engine.Blob
return nil, engine.InvalidParams.With(err)
}
// Validate the blobs from the pool and assemble the response
filled := 0
res := make([]*engine.BlobAndProofV2, len(hashes))
for i := range blobs {
// The blob has been evicted since the last AvailableBlobs call.
@ -649,10 +667,11 @@ func (api *ConsensusAPI) getBlobs(hashes []common.Hash, v2 bool) ([]*engine.Blob
Blob: blobs[i][:],
CellProofs: cellProofs,
}
filled++
}
if len(res) == len(hashes) {
if filled == len(hashes) {
getBlobsRequestCompleteHit.Inc(1)
} else if len(res) > 0 {
} else if filled > 0 {
getBlobsRequestPartialHit.Inc(1)
} else {
getBlobsRequestMiss.Inc(1)

View file

@ -23,7 +23,6 @@ import (
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/rpc"
)
@ -33,20 +32,18 @@ import (
type DownloaderAPI struct {
d *Downloader
chain *core.BlockChain
mux *event.TypeMux
installSyncSubscription chan chan interface{}
uninstallSyncSubscription chan *uninstallSyncSubscriptionRequest
}
// NewDownloaderAPI creates a new DownloaderAPI. The API has an internal event loop that
// listens for events from the downloader through the global event mux. In case it receives one of
// listens for events from the downloader through the event feed. In case it receives one of
// these events it broadcasts it to all syncing subscriptions that are installed through the
// installSyncSubscription channel.
func NewDownloaderAPI(d *Downloader, chain *core.BlockChain, m *event.TypeMux) *DownloaderAPI {
func NewDownloaderAPI(d *Downloader, chain *core.BlockChain) *DownloaderAPI {
api := &DownloaderAPI{
d: d,
chain: chain,
mux: m,
installSyncSubscription: make(chan chan interface{}),
uninstallSyncSubscription: make(chan *uninstallSyncSubscriptionRequest),
}
@ -66,7 +63,8 @@ func NewDownloaderAPI(d *Downloader, chain *core.BlockChain, m *event.TypeMux) *
// receive is {false}.
func (api *DownloaderAPI) eventLoop() {
var (
sub = api.mux.Subscribe(StartEvent{})
events = make(chan SyncEvent, 16)
sub = api.d.SubscribeSyncEvents(events)
syncSubscriptions = make(map[chan interface{}]struct{})
checkInterval = time.Second * 60
checkTimer = time.NewTimer(checkInterval)
@ -90,6 +88,7 @@ func (api *DownloaderAPI) eventLoop() {
}
)
defer checkTimer.Stop()
defer sub.Unsubscribe()
for {
select {
@ -101,14 +100,13 @@ func (api *DownloaderAPI) eventLoop() {
case u := <-api.uninstallSyncSubscription:
delete(syncSubscriptions, u.c)
close(u.uninstalled)
case event := <-sub.Chan():
if event == nil {
return
}
switch event.Data.(type) {
case StartEvent:
case ev := <-events:
if ev.Type == SyncStarted {
started = true
}
case <-sub.Err():
// The downloader is terminated or other internal error occurs
return
case <-checkTimer.C:
if !started {
checkTimer.Reset(checkInterval)

View file

@ -97,9 +97,12 @@ type headerTask struct {
}
type Downloader struct {
mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
moder *syncModer // Sync mode management, deliver the appropriate sync mode choice for each cycle
mux *event.TypeMux // Event multiplexer to announce sync operation events
mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
moder *syncModer // Sync mode management, deliver the appropriate sync mode choice for each cycle
// Event feed for downloader events
feed event.FeedOf[SyncEvent]
scope event.SubscriptionScope
queue *queue // Scheduler for selecting the hashes to download
peers *peerSet // Set of active peers from which download can proceed
@ -229,12 +232,11 @@ type BlockChain interface {
}
// New creates a new downloader to fetch hashes and blocks from remote peers.
func New(stateDb ethdb.Database, mode ethconfig.SyncMode, mux *event.TypeMux, chain BlockChain, dropPeer peerDropFn, success func()) *Downloader {
func New(stateDb ethdb.Database, mode ethconfig.SyncMode, chain BlockChain, dropPeer peerDropFn, success func()) *Downloader {
cutoffNumber, cutoffHash := chain.HistoryPruningCutoff()
dl := &Downloader{
stateDB: stateDb,
moder: newSyncModer(mode, chain, stateDb),
mux: mux,
queue: newQueue(blockCacheMaxItems, blockCacheInitialItems),
peers: newPeerSet(),
blockchain: chain,
@ -427,20 +429,25 @@ func (d *Downloader) ConfigSyncMode() SyncMode {
return d.moder.get(false)
}
// SubscribeSyncEvents creates a subscription for downloader sync events
func (d *Downloader) SubscribeSyncEvents(ch chan<- SyncEvent) event.Subscription {
return d.scope.Track(d.feed.Subscribe(ch))
}
// syncToHead starts a block synchronization based on the hash chain from
// the specified head hash.
func (d *Downloader) syncToHead() (err error) {
d.mux.Post(StartEvent{})
mode := d.getMode()
d.feed.Send(SyncEvent{Type: SyncStarted, Mode: mode})
defer func() {
// reset on error
if err != nil {
d.mux.Post(FailedEvent{err})
d.feed.Send(SyncEvent{Type: SyncFailed, Mode: mode, Err: err})
} else {
latest := d.blockchain.CurrentHeader()
d.mux.Post(DoneEvent{latest})
d.feed.Send(SyncEvent{Type: SyncCompleted, Mode: mode, Latest: latest})
}
}()
mode := d.getMode()
log.Debug("Backfilling with the network", "mode", mode)
defer func(start time.Time) {
@ -662,6 +669,9 @@ func (d *Downloader) Cancel() {
// Terminate interrupts the downloader, canceling all pending operations.
// The downloader cannot be reused after calling Terminate.
func (d *Downloader) Terminate() {
// Unsubscribe all subscriptions registered from downloader
d.scope.Close()
// Close the termination channel (make sure double close is allowed)
d.quitLock.Lock()
select {

View file

@ -32,7 +32,6 @@ import (
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/eth/protocols/snap"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
@ -75,7 +74,7 @@ func newTesterWithNotification(t *testing.T, mode ethconfig.SyncMode, success fu
chain: chain,
peers: make(map[string]*downloadTesterPeer),
}
tester.downloader = New(db, mode, new(event.TypeMux), tester.chain, tester.dropPeer, success)
tester.downloader = New(db, mode, tester.chain, tester.dropPeer, success)
return tester
}
@ -96,6 +95,7 @@ func (dl *downloadTester) newPeer(id string, version uint, blocks []*types.Block
id: id,
chain: newTestBlockchain(blocks),
withholdBodies: make(map[common.Hash]struct{}),
dropped: make(chan error, 1),
}
dl.peers[id] = peer
@ -121,8 +121,11 @@ func (dl *downloadTester) dropPeer(id string) {
type downloadTesterPeer struct {
dl *downloadTester
withholdBodies map[common.Hash]struct{}
corruptBodies bool // if set, the peer serves incorrect blocks
id string
chain *core.BlockChain
dropped chan error // signaled when res.Done receives an error
}
func unmarshalRlpHeaders(rlpdata []rlp.RawValue) []*types.Header {
@ -236,6 +239,11 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
txsHashes[i] = hash
uncleHashes[i] = types.CalcUncleHash(body.Uncles)
}
if dlp.corruptBodies {
for i := range txsHashes {
txsHashes[i] = common.Hash{0xff}
}
}
req := &eth.Request{
Peer: dlp.id,
}
@ -248,10 +256,16 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
WithdrawalRoots: withdrawalHashes,
},
Time: 1,
Done: make(chan error, 1), // Ignore the returned status
Done: make(chan error),
}
go func() {
sink <- res
if err := <-res.Done; err != nil {
select {
case dlp.dropped <- err:
default:
}
}
}()
return req, nil
}
@ -704,3 +718,21 @@ func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
t.Fatalf("Failed to sync chain in three seconds")
}
}
func TestInvalidBodyPeerDrop(t *testing.T) {
tester := newTester(t, FullSync)
defer tester.terminate()
chain := testChainBase.shorten(blockCacheMaxItems - 15)
peer := tester.newPeer("corrupt", eth.ETH69, chain.blocks[1:])
peer.corruptBodies = true
if err := tester.downloader.BeaconSync(chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
t.Fatalf("failed to beacon-sync chain: %v", err)
}
select {
case <-peer.dropped:
case <-time.After(1 * time.Minute):
t.Fatal("peer was not dropped")
}
}

View file

@ -16,10 +16,24 @@
package downloader
import "github.com/ethereum/go-ethereum/core/types"
import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/ethconfig"
)
type DoneEvent struct {
Latest *types.Header
// SyncEventType represents the type of sync event
type SyncEventType int
const (
SyncStarted SyncEventType = iota
SyncFailed
SyncCompleted
)
// SyncEvent represents a downloader synchronization event
type SyncEvent struct {
Type SyncEventType
Mode ethconfig.SyncMode
Err error // Set when Type is SyncFailed
Latest *types.Header // Set when Type is SyncCompleted
}
type StartEvent struct{}
type FailedEvent struct{ Err error }

View file

@ -323,25 +323,32 @@ func (d *Downloader) concurrentFetch(queue typedQueue) error {
delete(pending, res.Req.Peer)
delete(stales, res.Req.Peer)
// Signal the dispatcher that the round trip is done. We'll drop the
// peer if the data turns out to be junk.
res.Done <- nil
res.Req.Close()
// If the peer was previously banned and failed to deliver its pack
// in a reasonable time frame, ignore its message.
if peer := d.peers.Peer(res.Req.Peer); peer != nil {
// Deliver the received chunk of data and check chain validity
accepted, err := queue.deliver(peer, res)
if errors.Is(err, errInvalidChain) {
return err
}
// Unless a peer delivered something completely else than requested (usually
// caused by a timed out request which came through in the end), set it to
// idle. If the delivery's stale, the peer should have already been idled.
if !errors.Is(err, errStaleDelivery) {
queue.updateCapacity(peer, accepted, res.Time)
}
peer := d.peers.Peer(res.Req.Peer)
if peer == nil {
res.Done <- nil
res.Req.Close()
continue
}
// Deliver the received chunk of data and check chain validity
accepted, err := queue.deliver(peer, res)
// Unless a peer delivered something completely else than requested (usually
// caused by a timed out request which came through in the end), set it to
// idle. If the delivery's stale, the peer should have already been idled.
if !errors.Is(err, errStaleDelivery) {
queue.updateCapacity(peer, accepted, res.Time)
}
res.Done <- validityErrorOfRequest(err)
res.Req.Close()
if errors.Is(err, errInvalidChain) {
// errInvalidChain is the signal that processing of items failed internally,
// even though the items were validly encoded.
//
// This can be due to invalid blocks, or a database error.
// The sync cycle should be aborted for such errors, so we return it here.
return err
}
case cont := <-queue.waker():
@ -352,3 +359,11 @@ func (d *Downloader) concurrentFetch(queue typedQueue) error {
}
}
}
// validityErrorOfRequest returns err if it is related to block validity, and nil otherwise.
func validityErrorOfRequest(err error) error {
if errors.Is(err, errInvalidBody) || errors.Is(err, errInvalidReceipt) {
return err
}
return nil
}

View file

@ -671,10 +671,10 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
}
// Assemble each of the results with their headers and retrieved data parts
var (
accepted int
failure error
i int
hashes []common.Hash
accepted int
failure error
i int
foundStale bool
)
for _, header := range request.Headers {
// Short circuit assembly if no more fetch results are found
@ -686,42 +686,41 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
failure = err
break
}
hashes = append(hashes, header.Hash())
i++
}
for _, header := range request.Headers[:i] {
for k, header := range request.Headers[:i] {
if res, stale, err := q.resultCache.GetDeliverySlot(header.Number.Uint64()); err == nil && !stale {
reconstruct(accepted, res)
reconstruct(k, res)
accepted++
} else {
// else: between here and above, some other peer filled this result,
// Between here and above, some other peer filled this result,
// or it was indeed a no-op. This should not happen, but if it does it's
// not something to panic about
log.Error("Delivery stale", "stale", stale, "number", header.Number.Uint64(), "err", err)
failure = errStaleDelivery
foundStale = true
}
// Clean up a successful fetch
delete(taskPool, hashes[accepted])
accepted++
delete(taskPool, header.Hash())
}
resDropMeter.Mark(int64(results - accepted))
// Return all failed or missing fetches to the queue
for _, header := range request.Headers[accepted:] {
for _, header := range request.Headers[i:] {
taskQueue.Push(header, -int64(header.Number.Uint64()))
}
// Wake up Results
if accepted > 0 {
q.active.Signal()
}
if failure == nil {
return accepted, nil
if failure != nil {
return accepted, failure
}
// If none of the data was good, it's a stale delivery
if accepted > 0 {
return accepted, fmt.Errorf("partial failure: %v", failure)
if foundStale {
return accepted, errStaleDelivery
}
return accepted, fmt.Errorf("%w: %v", failure, errStaleDelivery)
return accepted, nil
}
// Prepare configures the result cache to allow accepting and caching inbound

View file

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/pathdb"
)
@ -59,6 +60,7 @@ var Defaults = Config{
StateHistory: pathdb.Defaults.StateHistory,
TrienodeHistory: pathdb.Defaults.TrienodeHistory,
NodeFullValueCheckpoint: pathdb.Defaults.FullValueCheckpoint,
BinTrieGroupDepth: triedb.DefaultBinTrieGroupDepth,
DatabaseCache: 2048,
TrieCleanCache: 614,
TrieDirtyCache: 1024,
@ -125,6 +127,11 @@ type Config struct {
// consistent with persistent state.
StateScheme string `toml:",omitempty"`
// BinTrieGroupDepth is the number of levels per serialized group in binary trie.
// Valid values are 1-8, with 8 being the default (byte-aligned groups).
// Lower values create smaller groups with more nodes.
BinTrieGroupDepth int `toml:",omitempty"`
// RequiredBlocks is a set of block number -> hash mappings which must be in the
// canonical chain of all remote peers. Setting the option makes geth verify the
// presence of these blocks for every new peer connection.

View file

@ -34,6 +34,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
TrienodeHistory int64 `toml:",omitempty"`
NodeFullValueCheckpoint uint32 `toml:",omitempty"`
StateScheme string `toml:",omitempty"`
BinTrieGroupDepth int `toml:",omitempty"`
RequiredBlocks map[uint64]common.Hash `toml:"-"`
SlowBlockThreshold time.Duration `toml:",omitempty"`
SkipBcVersionCheck bool `toml:"-"`
@ -87,6 +88,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.TrienodeHistory = c.TrienodeHistory
enc.NodeFullValueCheckpoint = c.NodeFullValueCheckpoint
enc.StateScheme = c.StateScheme
enc.BinTrieGroupDepth = c.BinTrieGroupDepth
enc.RequiredBlocks = c.RequiredBlocks
enc.SlowBlockThreshold = c.SlowBlockThreshold
enc.SkipBcVersionCheck = c.SkipBcVersionCheck
@ -144,6 +146,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
TrienodeHistory *int64 `toml:",omitempty"`
NodeFullValueCheckpoint *uint32 `toml:",omitempty"`
StateScheme *string `toml:",omitempty"`
BinTrieGroupDepth *int `toml:",omitempty"`
RequiredBlocks map[uint64]common.Hash `toml:"-"`
SlowBlockThreshold *time.Duration `toml:",omitempty"`
SkipBcVersionCheck *bool `toml:"-"`
@ -234,6 +237,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.StateScheme != nil {
c.StateScheme = *dec.StateScheme
}
if dec.BinTrieGroupDepth != nil {
c.BinTrieGroupDepth = *dec.BinTrieGroupDepth
}
if dec.RequiredBlocks != nil {
c.RequiredBlocks = dec.RequiredBlocks
}

View file

@ -22,13 +22,13 @@ import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
// Options are the contextual parameters to execute the requested call.
@ -70,17 +70,17 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
}
// Normalize the max fee per gas the call is willing to spend.
var feeCap *big.Int
var feeCap *uint256.Int
if call.GasFeeCap != nil {
feeCap = call.GasFeeCap
} else if call.GasPrice != nil {
feeCap = call.GasPrice
} else {
feeCap = common.Big0
feeCap = uint256.NewInt(0)
}
// Recap the highest gas limit with account's available balance.
if feeCap.BitLen() != 0 {
balance := opts.State.GetBalance(call.From).ToBig()
balance := opts.State.GetBalance(call.From).Clone()
available := balance
if call.Value != nil {
@ -90,8 +90,8 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
available.Sub(available, call.Value)
}
if opts.Config.IsCancun(opts.Header.Number, opts.Header.Time) && len(call.BlobHashes) > 0 {
blobGasPerBlob := new(big.Int).SetInt64(params.BlobTxBlobGasPerBlob)
blobBalanceUsage := new(big.Int).SetInt64(int64(len(call.BlobHashes)))
blobGasPerBlob := uint256.NewInt(params.BlobTxBlobGasPerBlob)
blobBalanceUsage := uint256.NewInt(uint64(len(call.BlobHashes)))
blobBalanceUsage.Mul(blobBalanceUsage, blobGasPerBlob)
blobBalanceUsage.Mul(blobBalanceUsage, call.BlobGasFeeCap)
if blobBalanceUsage.Cmp(available) >= 0 {
@ -99,13 +99,13 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
}
available.Sub(available, blobBalanceUsage)
}
allowance := new(big.Int).Div(available, feeCap)
allowance := new(uint256.Int).Div(available, feeCap)
// If the allowance is larger than maximum uint64, skip checking
if allowance.IsUint64() && hi > allowance.Uint64() {
transfer := call.Value
if transfer == nil {
transfer = new(big.Int)
transfer = new(uint256.Int)
}
log.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance,
"sent", transfer, "maxFeePerGas", feeCap, "fundable", allowance)

View file

@ -107,7 +107,6 @@ type handlerConfig struct {
Network uint64 // Network identifier to advertise
Sync ethconfig.SyncMode // Whether to snap or full sync
BloomCache uint64 // Megabytes to alloc for snap sync bloom
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
}
@ -126,7 +125,6 @@ type handler struct {
peers *peerSet
txBroadcastKey [16]byte
eventMux *event.TypeMux
txsCh chan core.NewTxsEvent
txsSub event.Subscription
blockRange *blockRangeState
@ -144,14 +142,9 @@ type handler struct {
// newHandler returns a handler for all Ethereum chain management protocol.
func newHandler(config *handlerConfig) (*handler, error) {
// Create the protocol manager with the base fields
if config.EventMux == nil {
config.EventMux = new(event.TypeMux) // Nicety initialization for tests
}
h := &handler{
nodeID: config.NodeID,
networkID: config.Network,
eventMux: config.EventMux,
database: config.Database,
txpool: config.TxPool,
chain: config.Chain,
@ -163,7 +156,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
handlerStartCh: make(chan struct{}),
}
// Construct the downloader (long sync)
h.downloader = downloader.New(config.Database, config.Sync, h.eventMux, h.chain, h.removePeer, h.enableSyncedFeatures)
h.downloader = downloader.New(config.Database, config.Sync, h.chain, h.removePeer, h.enableSyncedFeatures)
// If snap sync is requested but snapshots are disabled, fail loudly
if h.downloader.ConfigSyncMode() == ethconfig.SnapSync && (config.Chain.Snapshots() == nil && config.Chain.TrieDB().Scheme() == rawdb.HashScheme) {
@ -420,7 +413,7 @@ func (h *handler) Start(maxPeers int) {
// broadcast block range
h.wg.Add(1)
h.blockRange = newBlockRangeState(h.chain, h.eventMux)
h.blockRange = newBlockRangeState(h.chain, h.downloader)
go h.blockRangeLoop(h.blockRange)
// start sync handlers
@ -536,16 +529,19 @@ type blockRangeState struct {
next atomic.Pointer[eth.BlockRangeUpdatePacket]
headCh chan core.ChainHeadEvent
headSub event.Subscription
syncSub *event.TypeMuxSubscription
syncCh chan downloader.SyncEvent
syncSub event.Subscription
}
func newBlockRangeState(chain *core.BlockChain, typeMux *event.TypeMux) *blockRangeState {
func newBlockRangeState(chain *core.BlockChain, dl *downloader.Downloader) *blockRangeState {
headCh := make(chan core.ChainHeadEvent, chainHeadChanSize)
headSub := chain.SubscribeChainHeadEvent(headCh)
syncSub := typeMux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
syncCh := make(chan downloader.SyncEvent, 16)
syncSub := dl.SubscribeSyncEvents(syncCh)
st := &blockRangeState{
headCh: headCh,
headSub: headSub,
syncCh: syncCh,
syncSub: syncSub,
}
st.update(chain, chain.CurrentBlock())
@ -561,11 +557,8 @@ func (h *handler) blockRangeLoop(st *blockRangeState) {
for {
select {
case ev := <-st.syncSub.Chan():
if ev == nil {
continue
}
if _, ok := ev.Data.(downloader.StartEvent); ok && h.downloader.ConfigSyncMode() == ethconfig.SnapSync {
case ev := <-st.syncCh:
if ev.Type == downloader.SyncStarted && ev.Mode == ethconfig.SnapSync {
h.blockRangeWhileSnapSyncing(st)
}
case <-st.headCh:
@ -593,12 +586,8 @@ func (h *handler) blockRangeWhileSnapSyncing(st *blockRangeState) {
h.broadcastBlockRange(st)
}
// back to processing head block updates when sync is done
case ev := <-st.syncSub.Chan():
if ev == nil {
continue
}
switch ev.Data.(type) {
case downloader.FailedEvent, downloader.DoneEvent:
case ev := <-st.syncCh:
if ev.Type == downloader.SyncFailed || ev.Type == downloader.SyncCompleted {
return
}
// ignore head updates, but exit when the subscription ends

View file

@ -424,16 +424,20 @@ func testGetBlockBodies(t *testing.T, protocol uint) {
{0, []common.Hash{backend.chain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable
{0, []common.Hash{{}}, []bool{false}, 0}, // A non existent block should not be returned
// Existing and non-existing blocks interleaved should not cause problems
// Existing blocks followed by a non-existing one should stop at the gap
{0, []common.Hash{
backend.chain.GetBlockByNumber(1).Hash(),
backend.chain.GetBlockByNumber(10).Hash(),
backend.chain.GetBlockByNumber(100).Hash(),
{},
}, []bool{true, true, true, false}, 3},
// A non-existing block at the start should return nothing
{0, []common.Hash{
{},
backend.chain.GetBlockByNumber(1).Hash(),
{},
backend.chain.GetBlockByNumber(10).Hash(),
{},
backend.chain.GetBlockByNumber(100).Hash(),
{},
}, []bool{false, true, false, true, false, true, false}, 3},
}, []bool{false, true, true}, 0},
}
// Run each of the tests and verify the results against the chain
for i, tt := range tests {

View file

@ -238,10 +238,12 @@ func ServiceGetBlockBodiesQuery(chain *core.BlockChain, query GetBlockBodiesRequ
lookups >= 2*maxBodiesServe {
break
}
if data := chain.GetBodyRLP(hash); len(data) != 0 {
bodies = append(bodies, data)
bytes += len(data)
data := chain.GetBodyRLP(hash)
if len(data) == 0 {
break // If we don't have this block's body, stop serving.
}
bodies = append(bodies, data)
bytes += len(data)
}
return bodies
}
@ -281,16 +283,16 @@ func ServiceGetReceiptsQuery69(chain *core.BlockChain, query GetReceiptsRequest)
// Retrieve the requested block's receipts
results := chain.GetReceiptsRLP(hash)
if results == nil {
continue // Can't retrieve the receipts, so we just skip this block.
break // Don't have this block's receipts, stop serving.
}
body := chain.GetBodyRLP(hash)
if body == nil {
continue // The block body is missing, we also have to skip.
break // The block body is missing, stop serving.
}
results, _, err := blockReceiptsToNetwork(results, body, receiptQueryParams{})
if err != nil {
log.Error("Error in block receipts conversion", "hash", hash, "err", err)
continue
break
}
receipts.AppendRaw(results)
bytes += len(results)
@ -312,12 +314,13 @@ func serviceGetReceiptsQuery70(chain *core.BlockChain, query GetReceiptsRequest,
break
}
results := chain.GetReceiptsRLP(hash)
// If we don't have this block's receipts or body, stop serving.
if results == nil {
continue // Can't retrieve the receipts, so we just skip this block.
break
}
body := chain.GetBodyRLP(hash)
if body == nil {
continue // The block body is missing, we also have to skip.
break
}
q := receiptQueryParams{sizeLimit: uint64(maxPacketSize - bytes)}
if i == 0 {
@ -326,7 +329,7 @@ func serviceGetReceiptsQuery70(chain *core.BlockChain, query GetReceiptsRequest,
results, incomplete, err := blockReceiptsToNetwork(results, body, q)
if err != nil {
log.Error("Error in block receipts conversion", "hash", hash, "err", err)
continue
break
}
if results == nil {
// This case triggers when the first receipt of the block receipts list doesn't

View file

@ -248,13 +248,10 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
evm := vm.NewEVM(context, statedb, eth.blockchain.Config(), vm.Config{})
defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
}
// If prague hardfork, insert parent block hash in the state as per EIP-2935.
if eth.blockchain.Config().IsPrague(block.Number(), block.Time()) {
core.ProcessParentBlockHash(block.ParentHash(), evm)
}
// Run pre-execution system calls
core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), eth.blockchain.Config(), evm, block.Number(), block.Time())
if txIndex == 0 && len(block.Transactions()) == 0 {
return nil, context, statedb, release, nil
}

View file

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
@ -37,32 +38,40 @@ type syncReq struct {
errc chan error
}
type Config struct {
TargetBlock common.Hash // if set, sync is triggered at startup
ExitWhenSynced bool // if true, the node shuts down after sync has finished
}
// Syncer is an auxiliary service that allows Geth to perform full sync
// alone without consensus-layer attached. Users must specify a valid block hash
// as the sync target.
//
// Additionally, the syncer can be used to monitor state synchronization.
// It will exit once the specified target has been reached or when the
// most recent chain head is caught up.
//
// This tool can be applied to different networks, no matter it's pre-merge or
// post-merge, but only for full-sync.
type Syncer struct {
stack *node.Node
backend *eth.Ethereum
target common.Hash
request chan *syncReq
closed chan struct{}
wg sync.WaitGroup
exitWhenSynced bool
stack *node.Node
backend *eth.Ethereum
request chan *syncReq
closed chan struct{}
wg sync.WaitGroup
config Config
}
// Register registers the synchronization override service into the node
// stack for launching and stopping the service controlled by node.
func Register(stack *node.Node, backend *eth.Ethereum, target common.Hash, exitWhenSynced bool) (*Syncer, error) {
func Register(stack *node.Node, backend *eth.Ethereum, cfg Config) (*Syncer, error) {
s := &Syncer{
stack: stack,
backend: backend,
target: target,
request: make(chan *syncReq),
closed: make(chan struct{}),
exitWhenSynced: exitWhenSynced,
stack: stack,
backend: backend,
request: make(chan *syncReq),
closed: make(chan struct{}),
config: cfg,
}
stack.RegisterAPIs(s.APIs())
stack.RegisterLifecycle(s)
@ -88,9 +97,11 @@ func (s *Syncer) run() {
var (
target *types.Header
ticker = time.NewTicker(time.Second * 5)
syncCh = make(chan downloader.SyncEvent, 10)
)
defer ticker.Stop()
sub := s.backend.Downloader().SubscribeSyncEvents(syncCh)
defer sub.Unsubscribe()
for {
select {
case req := <-s.request:
@ -137,35 +148,50 @@ func (s *Syncer) run() {
}
}
case <-ticker.C:
if target == nil {
case ev := <-syncCh:
if ev.Type == downloader.SyncStarted {
log.Debug("Synchronization started")
continue
}
if ev.Type == downloader.SyncFailed {
log.Debug("Synchronization failed", "err", ev.Err)
continue
}
head := s.backend.BlockChain().CurrentHeader()
if head != nil {
// Set the finalized and safe markers relative to the current head.
// The finalized marker is set two epochs behind the target,
// and the safe marker is set one epoch behind the target.
if header := s.backend.BlockChain().GetHeaderByNumber(head.Number.Uint64() - params.EpochLength*2); header != nil {
if final := s.backend.BlockChain().CurrentFinalBlock(); final == nil || final.Number.Cmp(header.Number) < 0 {
s.backend.BlockChain().SetFinalized(header)
}
}
if header := s.backend.BlockChain().GetHeaderByNumber(head.Number.Uint64() - params.EpochLength); header != nil {
if safe := s.backend.BlockChain().CurrentSafeBlock(); safe == nil || safe.Number.Cmp(header.Number) < 0 {
s.backend.BlockChain().SetSafe(header)
}
}
}
// Terminate the node if the target has been reached
if s.exitWhenSynced {
if block := s.backend.BlockChain().GetBlockByHash(target.Hash()); block != nil {
log.Info("Sync target reached", "number", block.NumberU64(), "hash", block.Hash())
go s.stack.Close() // async since we need to close ourselves
return
if s.config.ExitWhenSynced {
var synced bool
var block *types.Header
if target != nil {
tb := s.backend.BlockChain().GetBlockByHash(target.Hash())
synced = tb != nil
block = tb.Header()
} else {
timestamp := time.Unix(int64(ev.Latest.Time), 0)
synced = time.Since(timestamp) < 10*time.Minute
block = ev.Latest
}
}
// Set the finalized and safe markers relative to the current head.
// The finalized marker is set two epochs behind the target,
// and the safe marker is set one epoch behind the target.
head := s.backend.BlockChain().CurrentHeader()
if head == nil {
continue
}
if header := s.backend.BlockChain().GetHeaderByNumber(head.Number.Uint64() - params.EpochLength*2); header != nil {
if final := s.backend.BlockChain().CurrentFinalBlock(); final == nil || final.Number.Cmp(header.Number) < 0 {
s.backend.BlockChain().SetFinalized(header)
}
}
if header := s.backend.BlockChain().GetHeaderByNumber(head.Number.Uint64() - params.EpochLength); header != nil {
if safe := s.backend.BlockChain().CurrentSafeBlock(); safe == nil || safe.Number.Cmp(header.Number) < 0 {
s.backend.BlockChain().SetSafe(header)
if synced {
log.Info("Sync target reached", "number", block.Number.Uint64(), "hash", block.Hash())
go s.stack.Close() // async since we need to close ourselves
}
}
@ -179,10 +205,10 @@ func (s *Syncer) run() {
func (s *Syncer) Start() error {
s.wg.Add(1)
go s.run()
if s.target == (common.Hash{}) {
if s.config.TargetBlock == (common.Hash{}) {
return nil
}
return s.Sync(s.target)
return s.Sync(s.config.TargetBlock)
}
// Stop terminates the synchronization service and stop all background activities.

View file

@ -372,13 +372,8 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
// as per EIP-4788.
context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil)
evm := vm.NewEVM(context, statedb, api.backend.ChainConfig(), vm.Config{})
if beaconRoot := next.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
}
// Insert parent hash in history contract.
if api.backend.ChainConfig().IsPrague(next.Number(), next.Time()) {
core.ProcessParentBlockHash(next.ParentHash(), evm)
}
core.PreExecution(ctx, next.BeaconRoot(), next.ParentHash(), api.backend.ChainConfig(), evm, next.Number(), next.Time())
evm.Release()
// Clean out any pending release functions of trace state. Note this
// step must be done after constructing tracing state, because the
@ -494,8 +489,8 @@ func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash,
return api.standardTraceBlockToFile(ctx, block, config)
}
// IntermediateRoots executes a block (bad- or canon- or side-), and returns a list
// of intermediate roots: the stateroot after each transaction.
// IntermediateRoots executes a block, and returns a list of intermediate roots:
// the stateroot after each transaction.
func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config *TraceConfig) ([]common.Hash, error) {
block, _ := api.blockByHash(ctx, hash)
if block == nil {
@ -517,21 +512,19 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
return nil, err
}
defer release()
var (
roots []common.Hash
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
chainConfig = api.backend.ChainConfig()
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
evm = vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
)
evm := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
}
if chainConfig.IsPrague(block.Number(), block.Time()) {
core.ProcessParentBlockHash(block.ParentHash(), evm)
}
// Run pre-execution system calls
core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), chainConfig, evm, block.Number(), block.Time())
for i, tx := range block.Transactions() {
if err := ctx.Err(); err != nil {
return nil, err
@ -548,7 +541,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
// N.B: This should never happen while tracing canon blocks, only when tracing bad blocks.
return roots, nil
}
// calling IntermediateRoot will internally call Finalize on the state
// Calling IntermediateRoot will internally call Finalize on the state
// so any modifications are written to the trie
roots = append(roots, statedb.IntermediateRoot(deleteEmptyObjects))
}
@ -587,12 +580,9 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
evm := vm.NewEVM(blockCtx, statedb, api.backend.ChainConfig(), vm.Config{})
defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
}
if api.backend.ChainConfig().IsPrague(block.Number(), block.Time()) {
core.ProcessParentBlockHash(block.ParentHash(), evm)
}
// Run pre-execution system calls
core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), api.backend.ChainConfig(), evm, block.Number(), block.Time())
// JS tracers have high overhead. In this case run a parallel
// process that generates states in one thread and traces txes
@ -760,15 +750,12 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
// Note: This copies the config, to not screw up the main config
chainConfig, canon = overrideConfig(chainConfig, config.Overrides)
}
evm := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
}
if chainConfig.IsPrague(block.Number(), block.Time()) {
core.ProcessParentBlockHash(block.ParentHash(), evm)
}
// Run pre-execution system calls
core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), chainConfig, evm, block.Number(), block.Time())
for i, tx := range block.Transactions() {
// Prepare the transaction for un-traced execution
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
@ -795,6 +782,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
return nil, err
}
dumps = append(dumps, dump.Name())
// Set up the tracer and EVM for the transaction.
var (
writer = bufio.NewWriter(dump)

View file

@ -112,9 +112,10 @@ type AccessListTracer struct {
func NewAccessListTracer(acl types.AccessList, addressesToExclude map[common.Address]struct{}) *AccessListTracer {
list := newAccessList()
for _, al := range acl {
if _, ok := addressesToExclude[al.Address]; !ok {
list.addAddress(al.Address)
if _, ok := addressesToExclude[al.Address]; ok {
continue
}
list.addAddress(al.Address)
for _, slot := range al.StorageKeys {
list.addSlot(al.Address, slot)
}

View file

@ -0,0 +1,39 @@
// Copyright 2026 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package logger
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
func TestNewAccessListTracerExcludedAddress(t *testing.T) {
excluded := common.HexToAddress("0x2222222222222222222222222222222222222222")
slot := common.HexToHash("0x01")
prelude := types.AccessList{{
Address: excluded,
StorageKeys: []common.Hash{slot},
}}
excl := map[common.Address]struct{}{excluded: {}}
tracer := NewAccessListTracer(prelude, excl)
got := tracer.AccessList()
if len(got) != 0 {
t.Fatalf("excluded prelude address must not contribute tuples, got %+v", got)
}
}

View file

@ -229,9 +229,9 @@ type StructLogger struct {
logs []json.RawMessage // buffer of json-encoded logs
resultSize int
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
skip bool // skip processing hooks.
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
skip bool // skip processing hooks.
}
// NewStreamingStructLogger returns a new streaming logger.
@ -357,8 +357,8 @@ func (l *StructLogger) OnExit(depth int, output []byte, gasUsed uint64, err erro
func (l *StructLogger) GetResult() (json.RawMessage, error) {
// Tracing aborted
if l.reason != nil {
return nil, l.reason
if p := l.reason.Load(); p != nil {
return nil, *p
}
failed := l.err != nil
returnData := common.CopyBytes(l.output)
@ -376,7 +376,7 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
// Stop terminates execution of the tracer at the first opportune moment.
func (l *StructLogger) Stop(err error) {
l.reason = err
l.reason.Store(&err)
l.interrupt.Store(true)
}

View file

@ -49,9 +49,9 @@ func init() {
// 0xc281d19e-0: 1
// }
type fourByteTracer struct {
ids map[string]int // ids aggregates the 4byte ids found
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
ids map[string]int // ids aggregates the 4byte ids found
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
chainConfig *params.ChainConfig
activePrecompiles []common.Address // Updated on tx start based on given rules
}
@ -124,12 +124,15 @@ func (t *fourByteTracer) GetResult() (json.RawMessage, error) {
if err != nil {
return nil, err
}
return res, t.reason
if p := t.reason.Load(); p != nil {
return res, *p
}
return res, nil
}
// Stop terminates execution of the tracer at the first opportune moment.
func (t *fourByteTracer) Stop(err error) {
t.reason = err
t.reason.Store(&err)
t.interrupt.Store(true)
}

View file

@ -116,8 +116,8 @@ type callTracer struct {
config callTracerConfig
gasLimit uint64
depth int
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
}
type callTracerConfig struct {
@ -268,12 +268,15 @@ func (t *callTracer) GetResult() (json.RawMessage, error) {
if err != nil {
return nil, err
}
return res, t.reason
if p := t.reason.Load(); p != nil {
return res, *p
}
return res, nil
}
// Stop terminates execution of the tracer at the first opportune moment.
func (t *callTracer) Stop(err error) {
t.reason = err
t.reason.Store(&err)
t.interrupt.Store(true)
}

View file

@ -233,7 +233,10 @@ func (t *flatCallTracer) GetResult() (json.RawMessage, error) {
if err != nil {
return nil, err
}
return res, t.tracer.reason
if p := t.tracer.reason.Load(); p != nil {
return res, *p
}
return res, nil
}
// Stop terminates execution of the tracer at the first opportune moment.

View file

@ -135,8 +135,8 @@ type opcodeWithPartialStack struct {
type erc7562Tracer struct {
config erc7562TracerConfig
gasLimit uint64
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
env *tracing.VMContext
ignoredOpcodes map[vm.OpCode]struct{}
@ -317,7 +317,10 @@ func (t *erc7562Tracer) OnLog(log1 *types.Log) {
// error arising from the encoding or forceful termination (via `Stop`).
func (t *erc7562Tracer) GetResult() (json.RawMessage, error) {
if t.interrupt.Load() {
return nil, t.reason
if p := t.reason.Load(); p != nil {
return nil, *p
}
return nil, nil
}
if len(t.callstackWithOpcodes) != 1 {
return nil, errors.New("incorrect number of top-level calls")
@ -337,12 +340,15 @@ func (t *erc7562Tracer) GetResult() (json.RawMessage, error) {
return nil, err
}
return enc, t.reason
if p := t.reason.Load(); p != nil {
return enc, *p
}
return enc, nil
}
// Stop terminates execution of the tracer at the first opportune moment.
func (t *erc7562Tracer) Stop(err error) {
t.reason = err
t.reason.Store(&err)
t.interrupt.Store(true)
}

View file

@ -63,22 +63,30 @@ func newMuxTracerFromConfig(ctx *tracers.Context, cfg json.RawMessage, chainConf
//
// The names parameter associates a label with each tracer, used as keys in
// the aggregated JSON result returned by GetResult.
//
// For hooks that have both a V1 and V2 form (OnCodeChange / OnCodeChangeV2,
// OnNonceChange / OnNonceChangeV2, OnSystemCallStart / OnSystemCallStartV2),
// the mux exposes only the V2 variant upward. The fanout then prefers each
// child's V2 hook and falls back to V1 if only V1 is set, mirroring the
// precedence already used in core/state_processor.go.
func NewMuxTracer(names []string, objects []*tracers.Tracer) (*tracers.Tracer, error) {
t := &muxTracer{names: names, tracers: objects}
return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
OnGasChange: t.OnGasChange,
OnBalanceChange: t.OnBalanceChange,
OnNonceChange: t.OnNonceChange,
OnCodeChange: t.OnCodeChange,
OnStorageChange: t.OnStorageChange,
OnLog: t.OnLog,
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
OnGasChange: t.OnGasChange,
OnBalanceChange: t.OnBalanceChange,
OnNonceChangeV2: t.OnNonceChangeV2,
OnCodeChangeV2: t.OnCodeChangeV2,
OnStorageChange: t.OnStorageChange,
OnLog: t.OnLog,
OnSystemCallStartV2: t.OnSystemCallStart,
OnSystemCallEnd: t.OnSystemCallEnd,
},
GetResult: t.GetResult,
Stop: t.Stop,
@ -149,26 +157,22 @@ func (t *muxTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason
}
}
func (t *muxTracer) OnNonceChange(a common.Address, prev, new uint64) {
func (t *muxTracer) OnNonceChangeV2(a common.Address, prev, new uint64, reason tracing.NonceChangeReason) {
for _, t := range t.tracers {
if t.OnNonceChange != nil {
if t.OnNonceChangeV2 != nil {
t.OnNonceChangeV2(a, prev, new, reason)
} else if t.OnNonceChange != nil {
t.OnNonceChange(a, prev, new)
}
}
}
func (t *muxTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
for _, t := range t.tracers {
if t.OnCodeChange != nil {
t.OnCodeChange(a, prevCodeHash, prev, codeHash, code)
}
}
}
func (t *muxTracer) OnCodeChangeV2(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte, reason tracing.CodeChangeReason) {
for _, t := range t.tracers {
if t.OnCodeChangeV2 != nil {
t.OnCodeChangeV2(a, prevCodeHash, prev, codeHash, code, reason)
} else if t.OnCodeChange != nil {
t.OnCodeChange(a, prevCodeHash, prev, codeHash, code)
}
}
}
@ -189,6 +193,24 @@ func (t *muxTracer) OnLog(log *types.Log) {
}
}
func (t *muxTracer) OnSystemCallStart(vm *tracing.VMContext) {
for _, t := range t.tracers {
if t.OnSystemCallStartV2 != nil {
t.OnSystemCallStartV2(vm)
} else if t.OnSystemCallStart != nil {
t.OnSystemCallStart()
}
}
}
func (t *muxTracer) OnSystemCallEnd() {
for _, t := range t.tracers {
if t.OnSystemCallEnd != nil {
t.OnSystemCallEnd()
}
}
}
// GetResult returns an empty json object.
func (t *muxTracer) GetResult() (json.RawMessage, error) {
resObject := make(map[string]json.RawMessage)

View file

@ -0,0 +1,87 @@
// Copyright 2026 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package native
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/eth/tracers"
)
// TestMuxForwardsV2StateHooks verifies that the mux tracer fans out the V2
// variants of state-change hooks to child tracers. A child tracer that only
// implements OnCodeChangeV2 / OnNonceChangeV2 must still receive events when
// wrapped behind the mux. The mux must also fall back to the V1 hook when a
// child only implements V1, mirroring the precedence used in
// core/state_processor.go.
func TestMuxForwardsV2StateHooks(t *testing.T) {
var (
codeV2Calls int
nonceV2Calls int
codeV1Calls int
nonceV1Calls int
)
v2Child := &tracers.Tracer{
Hooks: &tracing.Hooks{
OnCodeChangeV2: func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte, reason tracing.CodeChangeReason) {
codeV2Calls++
},
OnNonceChangeV2: func(addr common.Address, prev, new uint64, reason tracing.NonceChangeReason) {
nonceV2Calls++
},
},
}
v1Child := &tracers.Tracer{
Hooks: &tracing.Hooks{
OnCodeChange: func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) {
codeV1Calls++
},
OnNonceChange: func(addr common.Address, prev, new uint64) {
nonceV1Calls++
},
},
}
mux, err := NewMuxTracer([]string{"v2", "v1"}, []*tracers.Tracer{v2Child, v1Child})
if err != nil {
t.Fatalf("NewMuxTracer: %v", err)
}
if mux.Hooks.OnCodeChangeV2 == nil {
t.Fatal("mux does not expose OnCodeChangeV2; V2-only child tracers will miss code changes")
}
if mux.Hooks.OnNonceChangeV2 == nil {
t.Fatal("mux does not expose OnNonceChangeV2; V2-only child tracers will miss nonce changes")
}
mux.Hooks.OnCodeChangeV2(common.Address{}, common.Hash{}, nil, common.Hash{}, nil, tracing.CodeChangeContractCreation)
mux.Hooks.OnNonceChangeV2(common.Address{}, 0, 1, tracing.NonceChangeEoACall)
if codeV2Calls != 1 {
t.Fatalf("V2 child OnCodeChangeV2 got %d calls, want 1", codeV2Calls)
}
if nonceV2Calls != 1 {
t.Fatalf("V2 child OnNonceChangeV2 got %d calls, want 1", nonceV2Calls)
}
if codeV1Calls != 1 {
t.Fatalf("V1 child OnCodeChange got %d calls, want 1 (mux should fall back from V2 to V1)", codeV1Calls)
}
if nonceV1Calls != 1 {
t.Fatalf("V1 child OnNonceChange got %d calls, want 1 (mux should fall back from V2 to V1)", nonceV1Calls)
}
}

View file

@ -71,8 +71,8 @@ type prestateTracer struct {
to common.Address
config PrestateTracerConfig
chainConfig *params.ChainConfig
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
created map[common.Address]bool
deleted map[common.Address]bool
}
@ -240,12 +240,15 @@ func (t *prestateTracer) GetResult() (json.RawMessage, error) {
if err != nil {
return nil, err
}
return json.RawMessage(res), t.reason
if p := t.reason.Load(); p != nil {
return json.RawMessage(res), *p
}
return json.RawMessage(res), nil
}
// Stop terminates execution of the tracer at the first opportune moment.
func (t *prestateTracer) Stop(err error) {
t.reason = err
t.reason.Store(&err)
t.interrupt.Store(true)
}

View file

@ -0,0 +1,80 @@
// Copyright 2026 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package native_test
import (
"errors"
"math/big"
"sync"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
// TestTracerStopRace exercises the concurrent Stop / GetResult path that the
// trace RPC handler uses: a timeout watchdog goroutine calls Stop while the
// main goroutine is still running the trace and will eventually call
// GetResult. Under -race, writes to the interruption reason field must not
// race with reads, for every tracer that implements it.
//
// callTracer, flatCallTracer and erc7562Tracer's GetResult short-circuits on
// an empty callstack ("incorrect number of top-level calls") before loading
// the reason. For those tracers the test pushes a single top-level call frame
// via OnEnter so GetResult reaches the reason.Load() path where the race can
// be observed under -race.
func TestTracerStopRace(t *testing.T) {
type setup struct {
name string
needsFrame bool // whether GetResult requires a top-level call frame
}
cases := []setup{
{"callTracer", true},
{"flatCallTracer", true},
{"4byteTracer", false},
{"prestateTracer", false},
{"erc7562Tracer", true},
}
for _, s := range cases {
t.Run(s.name, func(t *testing.T) {
tr, err := tracers.DefaultDirectory.New(s.name, &tracers.Context{}, nil, params.MainnetChainConfig)
require.NoError(t, err)
if s.needsFrame && tr.OnEnter != nil {
// Push a single top-level call frame so GetResult doesn't
// short-circuit before reading the interruption reason.
tr.OnEnter(0, byte(vm.CALL), common.Address{}, common.Address{}, nil, 0, big.NewInt(0))
}
stopErr := errors.New("execution timeout")
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
tr.Stop(stopErr)
}()
go func() {
defer wg.Done()
_, _ = tr.GetResult()
}()
wg.Wait()
})
}
}

View file

@ -914,6 +914,7 @@ type SimulateCallResult struct {
ReturnValue []byte `json:"returnData"`
Logs []*types.Log `json:"logs"`
GasUsed uint64 `json:"gasUsed"`
MaxUsedGas uint64 `json:"maxUsedGas"`
Status uint64 `json:"status"`
Error *CallError `json:"error,omitempty"`
}
@ -921,6 +922,7 @@ type SimulateCallResult struct {
type simulateCallResultMarshaling struct {
ReturnValue hexutil.Bytes
GasUsed hexutil.Uint64
MaxUsedGas hexutil.Uint64
Status hexutil.Uint64
}

View file

@ -861,6 +861,12 @@ func TestSimulateV1(t *testing.T) {
if results[0].Calls[0].Error != nil {
t.Errorf("expected no error, got %v", results[0].Calls[0].Error)
}
if results[0].Calls[0].MaxUsedGas == 0 {
t.Error("expected maxUsedGas to be set")
}
if results[0].Calls[0].MaxUsedGas < results[0].Calls[0].GasUsed {
t.Errorf("expected maxUsedGas >= gasUsed, got %d < %d", results[0].Calls[0].MaxUsedGas, results[0].Calls[0].GasUsed)
}
}
func TestSimulateV1WithBlockOverrides(t *testing.T) {

View file

@ -17,6 +17,7 @@ func (s SimulateCallResult) MarshalJSON() ([]byte, error) {
ReturnValue hexutil.Bytes `json:"returnData"`
Logs []*types.Log `json:"logs"`
GasUsed hexutil.Uint64 `json:"gasUsed"`
MaxUsedGas hexutil.Uint64 `json:"maxUsedGas"`
Status hexutil.Uint64 `json:"status"`
Error *CallError `json:"error,omitempty"`
}
@ -24,6 +25,7 @@ func (s SimulateCallResult) MarshalJSON() ([]byte, error) {
enc.ReturnValue = s.ReturnValue
enc.Logs = s.Logs
enc.GasUsed = hexutil.Uint64(s.GasUsed)
enc.MaxUsedGas = hexutil.Uint64(s.MaxUsedGas)
enc.Status = hexutil.Uint64(s.Status)
enc.Error = s.Error
return json.Marshal(&enc)
@ -35,6 +37,7 @@ func (s *SimulateCallResult) UnmarshalJSON(input []byte) error {
ReturnValue *hexutil.Bytes `json:"returnData"`
Logs []*types.Log `json:"logs"`
GasUsed *hexutil.Uint64 `json:"gasUsed"`
MaxUsedGas *hexutil.Uint64 `json:"maxUsedGas"`
Status *hexutil.Uint64 `json:"status"`
Error *CallError `json:"error,omitempty"`
}
@ -51,6 +54,9 @@ func (s *SimulateCallResult) UnmarshalJSON(input []byte) error {
if dec.GasUsed != nil {
s.GasUsed = uint64(*dec.GasUsed)
}
if dec.MaxUsedGas != nil {
s.MaxUsedGas = uint64(*dec.MaxUsedGas)
}
if dec.Status != nil {
s.Status = uint64(*dec.Status)
}

View file

@ -86,6 +86,8 @@ func NewBackend(alloc types.GenesisAlloc, options ...func(nodeConf *node.Config,
}
ethConf.SyncMode = ethconfig.FullSync
ethConf.TxPool.NoLocals = true
// Disable log indexing to force unindexed log search
ethConf.LogNoHistory = true
for _, option := range options {
option(&nodeConf, &ethConf)

View file

@ -41,12 +41,19 @@ type GoToolchain struct {
func (g *GoToolchain) Go(command string, args ...string) *exec.Cmd {
tool := g.goTool(command, args...)
// Configure environment for cross build.
if g.GOARCH != "" && g.GOARCH != runtime.GOARCH {
// Configure environment for cross build. Force CGO_ENABLED=1 whenever
// either GOOS or GOARCH differs from the host: Go's default is
// CGO_ENABLED=0 for any cross-compile, but geth's release builds rely
// on cgo (c-kzg-4844, secp256k1) regardless of which axis is crossing.
crossArch := g.GOARCH != "" && g.GOARCH != runtime.GOARCH
crossOS := g.GOOS != "" && g.GOOS != runtime.GOOS
if crossArch || crossOS {
tool.Env = append(tool.Env, "CGO_ENABLED=1")
}
if crossArch {
tool.Env = append(tool.Env, "GOARCH="+g.GOARCH)
}
if g.GOOS != "" && g.GOOS != runtime.GOOS {
if crossOS {
tool.Env = append(tool.Env, "GOOS="+g.GOOS)
}
// Configure C compiler.

View file

@ -22,6 +22,7 @@ import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"iter"
@ -180,12 +181,13 @@ func (db *ChecksumDB) DownloadFile(url, dstPath string) error {
return fmt.Errorf("no known hash for file %q", basename)
}
// Shortcut if already downloaded.
if verifyHash(dstPath, hash) == nil {
if err := verifyHash(dstPath, hash); err == nil {
fmt.Printf("%s is up-to-date\n", dstPath)
return nil
} else if !errors.Is(err, os.ErrNotExist) {
fmt.Printf("%s is stale\n", dstPath)
}
fmt.Printf("%s is stale\n", dstPath)
fmt.Printf("downloading from %s\n", url)
resp, err := http.Get(url)
if err != nil {
@ -209,9 +211,12 @@ func (db *ChecksumDB) DownloadFile(url, dstPath string) error {
if resp.ContentLength > 0 {
dst = newDownloadWriter(fd, resp.ContentLength)
}
_, err = io.Copy(dst, resp.Body)
dst.Close()
if err != nil {
if _, err = io.Copy(dst, resp.Body); err != nil {
dst.Close()
os.Remove(tmpfile)
return err
}
if err = dst.Close(); err != nil {
os.Remove(tmpfile)
return err
}

View file

@ -156,22 +156,22 @@ func (it *RawIterator) Next() bool {
var n int64
if it.Header, n, it.err = newSnappyReader(it.e.s, era.TypeCompressedHeader, off); it.err != nil {
it.clear()
return true
return false
}
off += n
if it.Body, n, it.err = newSnappyReader(it.e.s, era.TypeCompressedBody, off); it.err != nil {
it.clear()
return true
return false
}
off += n
if it.Receipts, n, it.err = newSnappyReader(it.e.s, era.TypeCompressedReceipts, off); it.err != nil {
it.clear()
return true
return false
}
off += n
if it.TotalDifficulty, _, it.err = it.e.s.ReaderAt(era.TypeTotalDifficulty, off); it.err != nil {
it.clear()
return true
return false
}
it.next += 1
return true

View file

@ -734,6 +734,10 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
if err := blockOverrides.Apply(&blockCtx); err != nil {
return nil, err
}
// Override the header so callers that compute gas price from 1559 fee
// fields see the overridden basefee. Otherwise GASPRICE/effectiveTip
// would be derived from the pre-override basefee.
header = blockOverrides.MakeHeader(header)
}
rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time)
precompiles := vm.ActivePrecompiledContracts(rules)
@ -992,6 +996,9 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} {
if head.RequestsHash != nil {
result["requestsHash"] = head.RequestsHash
}
if head.BlockAccessListHash != nil {
result["balHash"] = head.BlockAccessListHash
}
if head.SlotNumber != nil {
result["slotNumber"] = hexutil.Uint64(*head.SlotNumber)
}

View file

@ -1315,6 +1315,27 @@ func TestCall(t *testing.T) {
},
expectErr: errors.New(`block override "withdrawals" is not supported for this RPC method`),
},
// Verify that an overridden basefee is honored when computing gasPrice
// from the 1559 fee fields. Returning GASPRICE opcode; expected value
// is min(MaxFeePerGas, MaxPriorityFeePerGas + overridden BaseFee).
//
// BaseFee override = 0xa (10); MaxFeePerGas = 0x64 (100);
// MaxPriorityFeePerGas = 0x2 (2); expected GASPRICE = 12.
{
name: "basefee-override-used-in-gasprice",
blockNumber: rpc.LatestBlockNumber,
call: TransactionArgs{
From: &accounts[0].addr,
// Contract: GASPRICE; PUSH1 0; MSTORE; PUSH1 32; PUSH1 0; RETURN
Input: hex2Bytes("3a60005260206000f3"),
MaxFeePerGas: (*hexutil.Big)(big.NewInt(100)),
MaxPriorityFeePerGas: (*hexutil.Big)(big.NewInt(2)),
},
blockOverrides: override.BlockOverrides{
BaseFeePerGas: (*hexutil.Big)(big.NewInt(10)),
},
want: "0x000000000000000000000000000000000000000000000000000000000000000c",
},
}
for _, tc := range testSuite {
result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides)
@ -2659,6 +2680,67 @@ func TestSimulateV1TxSender(t *testing.T) {
require.Equal(t, sender2, summary[1].Transactions[0].From, "sender address mismatch")
}
// TestSimulateV1WithdrawalsByFork verifies that withdrawals and withdrawalsRoot
// are only emitted in the simulated block result when the simulated block is
// post-Shanghai. Pre-Shanghai blocks must omit both fields, otherwise the
// header hash and size would not match a valid pre-Shanghai block.
func TestSimulateV1WithdrawalsByFork(t *testing.T) {
t.Parallel()
run := func(t *testing.T, cfg *params.ChainConfig, blockTime *uint64, wantWithdrawals bool) {
t.Helper()
gspec := &core.Genesis{Config: cfg, Alloc: types.GenesisAlloc{}}
backend := newTestBackend(t, 1, gspec, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {})
ctx := context.Background()
stateDB, baseHeader, err := backend.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber))
if err != nil {
t.Fatalf("failed to get state and header: %v", err)
}
sim := &simulator{
b: backend,
state: stateDB,
base: baseHeader,
chainConfig: backend.ChainConfig(),
budget: newGasBudget(0),
}
block := simBlock{}
if blockTime != nil {
t := hexutil.Uint64(*blockTime)
block.BlockOverrides = &override.BlockOverrides{Time: &t}
}
results, err := sim.execute(ctx, []simBlock{block})
if err != nil {
t.Fatalf("simulation execution failed: %v", err)
}
require.Len(t, results, 1)
enc, err := json.Marshal(results[0])
if err != nil {
t.Fatalf("failed to marshal result: %v", err)
}
var raw map[string]json.RawMessage
if err := json.Unmarshal(enc, &raw); err != nil {
t.Fatalf("failed to unmarshal result: %v", err)
}
_, hasWithdrawals := raw["withdrawals"]
_, hasWithdrawalsRoot := raw["withdrawalsRoot"]
if hasWithdrawals != wantWithdrawals || hasWithdrawalsRoot != wantWithdrawals {
t.Fatalf("unexpected withdrawals fields: withdrawals=%v withdrawalsRoot=%v want=%v\n%s", hasWithdrawals, hasWithdrawalsRoot, wantWithdrawals, enc)
}
}
t.Run("pre-shanghai", func(t *testing.T) {
// TestChainConfig has ShanghaiTime=nil, so all simulated blocks are pre-Shanghai.
run(t, params.TestChainConfig, nil, false)
})
t.Run("post-shanghai", func(t *testing.T) {
// MergedTestChainConfig has every fork active from genesis.
run(t, params.MergedTestChainConfig, nil, true)
})
}
func TestSignTransaction(t *testing.T) {
t.Parallel()
// Initialize test accounts

View file

@ -318,12 +318,9 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
if precompiles != nil {
evm.SetPrecompiles(precompiles)
}
if sim.chainConfig.IsPrague(header.Number, header.Time) || sim.chainConfig.IsUBT(header.Number, header.Time) {
core.ProcessParentBlockHash(header.ParentHash, evm)
}
if header.ParentBeaconRoot != nil {
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, evm)
}
// Run pre-execution system calls
core.PreExecution(ctx, header.ParentBeaconRoot, header.ParentHash, sim.chainConfig, evm, header.Number, header.Time)
var allLogs []*types.Log
for i, call := range block.Calls {
// Terminate if the context is cancelled
@ -393,22 +390,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
header.BlobGasUsed = &blobGasUsed
}
// Process EIP-7685 requests
var requests [][]byte
if sim.chainConfig.IsPrague(header.Number, header.Time) {
requests = [][]byte{}
// EIP-6110
if err := core.ParseDepositLogs(&requests, allLogs, sim.chainConfig); err != nil {
return nil, nil, nil, err
}
// EIP-7002
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
return nil, nil, nil, err
}
// EIP-7251
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
return nil, nil, nil, err
}
// Run post-execution system calls
requests, err := core.PostExecution(ctx, sim.chainConfig, header.Number, header.Time, allLogs, evm)
if err != nil {
return nil, nil, nil, err
}
if requests != nil {
reqHash := types.CalcRequestsHash(requests)
@ -417,7 +402,12 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
blockBody := &types.Body{
Transactions: txes,
Withdrawals: *block.BlockOverrides.Withdrawals, // Withdrawal is also sanitized as non-nil
}
// Withdrawals are a post-Shanghai field. Attaching a non-nil withdrawals
// slice would cause types.NewBlock to populate WithdrawalsHash on the
// header and emit withdrawals fields for pre-Shanghai blocks.
if sim.chainConfig.IsShanghai(header.Number, header.Time) {
blockBody.Withdrawals = *block.BlockOverrides.Withdrawals
}
chainHeadReader := &simChainHeadReader{ctx, sim.b}

View file

@ -446,27 +446,27 @@ func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int,
// Assumes that fields are not nil, i.e. setDefaults or CallDefaults has been called.
func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck bool) *core.Message {
var (
gasPrice *big.Int
gasFeeCap *big.Int
gasTipCap *big.Int
gasPrice *uint256.Int
gasFeeCap *uint256.Int
gasTipCap *uint256.Int
)
if baseFee == nil {
gasPrice = args.GasPrice.ToInt()
gasPrice, _ = args.GasPrice.ToUint256()
gasFeeCap, gasTipCap = gasPrice, gasPrice
} else {
// A basefee is provided, necessitating 1559-type execution
if args.GasPrice != nil {
// User specified the legacy gas field, convert to 1559 gas typing
gasPrice = args.GasPrice.ToInt()
gasPrice, _ = args.GasPrice.ToUint256()
gasFeeCap, gasTipCap = gasPrice, gasPrice
} else {
// User specified 1559 gas fields (or none), use those
gasFeeCap = args.MaxFeePerGas.ToInt()
gasTipCap = args.MaxPriorityFeePerGas.ToInt()
gasFeeCap, _ = args.MaxFeePerGas.ToUint256()
gasTipCap, _ = args.MaxPriorityFeePerGas.ToUint256()
// Backfill the legacy gasPrice for EVM execution, unless we're all zeroes
gasPrice = new(big.Int)
gasPrice = uint256.NewInt(0)
if gasFeeCap.BitLen() > 0 || gasTipCap.BitLen() > 0 {
gasPrice = gasPrice.Add(gasTipCap, baseFee)
gasPrice = gasPrice.Add(gasTipCap, uint256.MustFromBig(baseFee))
if gasPrice.Cmp(gasFeeCap) > 0 {
gasPrice = gasFeeCap
}
@ -477,10 +477,12 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck bool) *c
if args.AccessList != nil {
accessList = *args.AccessList
}
value, _ := args.Value.ToUint256()
blobFeeCap, _ := args.BlobFeeCap.ToUint256()
return &core.Message{
From: args.from(),
To: args.To,
Value: (*big.Int)(args.Value),
Value: value,
Nonce: uint64(*args.Nonce),
GasLimit: uint64(*args.Gas),
GasPrice: gasPrice,
@ -488,7 +490,7 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck bool) *c
GasTipCap: gasTipCap,
Data: args.data(),
AccessList: accessList,
BlobGasFeeCap: (*big.Int)(args.BlobFeeCap),
BlobGasFeeCap: blobFeeCap,
BlobHashes: args.BlobHashes,
SetCodeAuthorizations: args.AuthorizationList,
SkipNonceChecks: skipNonceCheck,

View file

@ -208,21 +208,9 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams,
}
// Collect consensus-layer requests if Prague is enabled.
var requests [][]byte
if miner.chainConfig.IsPrague(work.header.Number, work.header.Time) {
requests = [][]byte{}
// EIP-6110 deposits
if err := core.ParseDepositLogs(&requests, allLogs, miner.chainConfig); err != nil {
return &newPayloadResult{err: err}
}
// EIP-7002
if err := core.ProcessWithdrawalQueue(&requests, work.evm); err != nil {
return &newPayloadResult{err: err}
}
// EIP-7251 consolidations
if err := core.ProcessConsolidationQueue(&requests, work.evm); err != nil {
return &newPayloadResult{err: err}
}
requests, err := core.PostExecution(ctx, miner.chainConfig, work.header.Number, work.header.Time, allLogs, work.evm)
if err != nil {
return &newPayloadResult{err: err}
}
if requests != nil {
reqHash := types.CalcRequestsHash(requests)
@ -329,12 +317,8 @@ func (miner *Miner) prepareWork(ctx context.Context, genParams *generateParams,
log.Error("Failed to create sealing context", "err", err)
return nil, err
}
if header.ParentBeaconRoot != nil {
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, env.evm)
}
if miner.chainConfig.IsPrague(header.Number, header.Time) {
core.ProcessParentBlockHash(header.ParentHash, env.evm)
}
// Run pre-execution system calls
core.PreExecution(ctx, header.ParentBeaconRoot, header.ParentHash, miner.chainConfig, env.evm, header.Number, header.Time)
return env, nil
}

View file

@ -35,7 +35,6 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
@ -44,7 +43,6 @@ import (
// Node is a container on which services can be registered.
type Node struct {
eventmux *event.TypeMux
config *Config
accman *accounts.Manager
log log.Logger
@ -108,7 +106,6 @@ func New(conf *Config) (*Node, error) {
node := &Node{
config: conf,
inprocHandler: server,
eventmux: new(event.TypeMux),
log: conf.Logger,
stop: make(chan struct{}),
server: &p2p.Server{Config: conf.P2P},
@ -692,12 +689,6 @@ func (n *Node) WSAuthEndpoint() string {
return "ws://" + n.wsAuth.listenAddr() + n.wsAuth.wsConfig.prefix
}
// EventMux retrieves the event multiplexer used by all the network services in
// the current protocol stack.
func (n *Node) EventMux() *event.TypeMux {
return n.eventmux
}
// OpenDatabaseWithOptions opens an existing database with the given name (or creates one if no
// previous can be found) from within the node's instance directory. If the node has no
// data directory, an in-memory database is returned.

View file

@ -67,7 +67,10 @@ type tcpDialer struct {
}
func (t tcpDialer) Dial(ctx context.Context, dest *enode.Node) (net.Conn, error) {
addr, _ := dest.TCPEndpoint()
addr, ok := dest.TCPEndpoint()
if !ok {
return nil, errNoPort
}
return t.d.DialContext(ctx, "tcp", addr.String())
}

View file

@ -753,6 +753,41 @@ func (tab *Table) deleteNode(n *enode.Node) {
// waitForNodes blocks until the table contains at least n nodes.
func (tab *Table) waitForNodes(ctx context.Context, n int) error {
// Wrap ctx so the forwarder goroutine exits when waitForNodes returns,
// regardless of whether the caller's ctx is canceled.
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// Set up a notification channel that gets unblocked when there was any activity on
// the table. Ultimately this reads from the table's nodeFeed, but can't use the feed
// directly on the same goroutine that takes Table.mutex, it would deadlock.
var notify chan struct{}
var notifyErr error
initsub := func() event.Subscription {
notify = make(chan struct{}, 1)
newnode := make(chan *enode.Node, 1)
sub := tab.nodeFeed.Subscribe(newnode)
go func() {
defer close(notify)
for {
select {
case <-newnode:
select {
case notify <- struct{}{}:
default:
}
case <-ctx.Done():
notifyErr = ctx.Err()
return
case <-tab.closeReq:
notifyErr = errClosed
return
}
}
}()
return sub
}
getlength := func() (count int) {
for _, b := range &tab.buckets {
count += len(b.entries)
@ -760,28 +795,24 @@ func (tab *Table) waitForNodes(ctx context.Context, n int) error {
return count
}
var ch chan *enode.Node
for {
tab.mutex.Lock()
if getlength() >= n {
tab.mutex.Unlock()
return nil
}
if ch == nil {
// Init subscription.
ch = make(chan *enode.Node)
sub := tab.nodeFeed.Subscribe(ch)
if notify == nil {
// Lazily init the subscription. Do this while holding the
// lock so we don't miss any events that change the node count.
sub := initsub()
defer sub.Unsubscribe()
}
tab.mutex.Unlock()
// Wait for a node add event.
select {
case <-ch:
case <-ctx.Done():
return ctx.Err()
case <-tab.closeReq:
return errClosed
// Wait for table event.
if _, ok := <-notify; !ok {
break
}
}
return notifyErr
}

View file

@ -17,6 +17,7 @@
package discover
import (
"context"
"crypto/ecdsa"
"fmt"
"math/rand"
@ -550,6 +551,45 @@ func TestSetFallbackNodes_DNSHostname(t *testing.T) {
t.Logf("resolved localhost to %v", resolved.IPAddr())
}
// This test checks that waitForNodes does not block addFoundNode.
// See https://github.com/ethereum/go-ethereum/issues/34881.
func TestTable_waitForNodesLocking(t *testing.T) {
transport := newPingRecorder()
tab, db := newTestTable(transport, Config{})
defer db.Close()
defer tab.close()
<-tab.initDone
// waitForNodes will never reach this count, so it stays subscribed
// to nodeFeed and looping for the duration of the test.
waitCtx, cancelWait := context.WithCancel(context.Background())
defer cancelWait()
waitDone := make(chan struct{})
go func() {
defer close(waitDone)
tab.waitForNodes(waitCtx, 1<<20)
}()
// Call addFoundNode in loop to send to the feed.
addDone := make(chan struct{})
go func() {
defer close(addDone)
for i := range 10000 {
d := 240 + (i % 17)
n := nodeAtDistance(tab.self().ID(), d, intIP(i))
tab.addFoundNode(n, true)
}
}()
select {
case <-addDone:
cancelWait()
<-waitDone
case <-time.After(10 * time.Second):
t.Fatal("deadlock detected: add loop did not finish within 10s")
}
}
func newkey() *ecdsa.PrivateKey {
key, err := crypto.GenerateKey()
if err != nil {

View file

@ -447,6 +447,7 @@ func (t *UDPv4) loop() {
// Start the timer so it fires when the next pending reply has expired.
now := time.Now()
for p, el := range iterList[*replyMatcher](plist) {
nextTimeout = p
if dist := p.deadline.Sub(now); dist < 2*respTimeout {
timeout.Reset(dist)
return
@ -454,7 +455,7 @@ func (t *UDPv4) loop() {
// Remove pending replies whose deadline is too far in the
// future. These can occur if the system clock jumped
// backwards after the deadline was assigned.
nextTimeout.errc <- errClockWarp
p.errc <- errClockWarp
plist.Remove(el)
}
nextTimeout = nil
@ -554,8 +555,9 @@ func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) {
if err := t.handlePacket(from, buf[:nbytes]); err != nil && unhandled == nil {
t.log.Debug("Bad discv4 packet", "addr", from, "err", err)
} else if err != nil && unhandled != nil {
p := ReadPacket{bytes.Clone(buf[:nbytes]), from}
select {
case unhandled <- ReadPacket{buf[:nbytes], from}:
case unhandled <- p:
default:
}
}

View file

@ -17,6 +17,7 @@
package core
import (
"bytes"
"context"
"encoding/json"
"errors"
@ -309,7 +310,8 @@ func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hex
if sig[64] != 27 && sig[64] != 28 {
return common.Address{}, errors.New("invalid Ethereum signature (V is not 27 or 28)")
}
sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
sig = bytes.Clone(sig) // Avoid mutating the input
sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
hash := accounts.TextHash(data)
rpk, err := crypto.SigToPub(hash, sig)
if err != nil {

View file

@ -35,7 +35,6 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/holiman/uint256"
)
func initMatcher(st *testMatcher) {
@ -329,7 +328,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
initialGas := vm.NewGasBudget(msg.GasLimit)
// Execute the message.
_, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas.Copy(), uint256.MustFromBig(msg.Value))
_, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas.Copy(), msg.Value)
if err != nil {
b.Error(err)
return

View file

@ -479,15 +479,15 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess
From: from,
To: to,
Nonce: tx.Nonce,
Value: value,
Value: uint256.MustFromBig(value),
GasLimit: gasLimit,
GasPrice: gasPrice,
GasFeeCap: tx.MaxFeePerGas,
GasTipCap: tx.MaxPriorityFeePerGas,
GasPrice: uint256.MustFromBig(gasPrice),
GasFeeCap: uint256.MustFromBig(tx.MaxFeePerGas),
GasTipCap: uint256.MustFromBig(tx.MaxPriorityFeePerGas),
Data: data,
AccessList: accessList,
BlobHashes: tx.BlobVersionedHashes,
BlobGasFeeCap: tx.BlobGasFeeCap,
BlobGasFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap),
SetCodeAuthorizations: authList,
}
return msg, nil

View file

@ -81,7 +81,7 @@ func (tt *TransactionTest) Run() error {
return
}
// Intrinsic gas
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
if err != nil {
return
}
@ -92,7 +92,7 @@ func (tt *TransactionTest) Run() error {
if rules.IsPrague {
var floorDataGas uint64
floorDataGas, err = core.FloorDataGas(rules, tx.Data())
floorDataGas, err = core.FloorDataGas(rules, tx.Data(), tx.AccessList())
if err != nil {
return
}

View file

@ -27,8 +27,19 @@ const (
NodeTypeBytes = 1 // Size of node type prefix in serialization
HashSize = 32 // Size of a hash in bytes
StemBitmapSize = 32 // Size of the bitmap in a stem node (256 values = 32 bytes)
MaxGroupDepth = 8
)
// bitmapSizeForDepth returns the bitmap size in bytes for a given group depth.
// For depths 1-3, returns 1 byte. For depths 4-8, returns 2^(depth-3) bytes.
func bitmapSizeForDepth(groupDepth int) int {
if groupDepth <= 3 {
return 1
}
return 1 << (groupDepth - 3)
}
const (
nodeTypeStem = iota + 1
nodeTypeInternal

View file

@ -23,8 +23,8 @@ import (
"github.com/ethereum/go-ethereum/common"
)
// TestSerializeDeserializeInternalNode tests flat 65-byte serialization and
// deserialization of InternalNode through nodeStore.
// TestSerializeDeserializeInternalNode tests grouped serialization and
// deserialization of InternalNode through nodeStore at groupDepth=1.
func TestSerializeDeserializeInternalNode(t *testing.T) {
leftHash := common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
rightHash := common.HexToHash("0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321")
@ -39,24 +39,32 @@ func TestSerializeDeserializeInternalNode(t *testing.T) {
rootNode.right = rightRef
s.root = rootRef
// Serialize the node — flat 65-byte format
serialized := s.serializeNode(rootRef)
// Serialize the node — grouped format at groupDepth=1:
// [type(1)][groupDepth(1)][bitmap(1)][leftHash(32)][rightHash(32)] = 67 bytes
serialized := s.serializeNode(rootRef, 1)
// Check the serialized format: [type(1)][leftHash(32)][rightHash(32)]
if serialized[0] != nodeTypeInternal {
t.Errorf("Expected type byte to be %d, got %d", nodeTypeInternal, serialized[0])
}
if serialized[1] != 1 {
t.Errorf("Expected groupDepth byte to be 1, got %d", serialized[1])
}
expectedLen := NodeTypeBytes + 2*HashSize // 1 + 64 = 65
expectedLen := NodeTypeBytes + 1 + 1 + 2*HashSize // type + groupDepth + bitmap + 2 hashes = 67
if len(serialized) != expectedLen {
t.Errorf("Expected serialized length to be %d, got %d", expectedLen, len(serialized))
}
// Check that left and right hashes are embedded directly
if !bytes.Equal(serialized[NodeTypeBytes:NodeTypeBytes+HashSize], leftHash[:]) {
// Both children present at a 1-level group → bitmap byte = 0b11000000.
if serialized[2] != 0xc0 {
t.Errorf("Expected bitmap byte 0xc0, got 0x%02x", serialized[2])
}
hashesStart := NodeTypeBytes + 1 + 1
if !bytes.Equal(serialized[hashesStart:hashesStart+HashSize], leftHash[:]) {
t.Error("Left hash not found at expected position")
}
if !bytes.Equal(serialized[NodeTypeBytes+HashSize:], rightHash[:]) {
if !bytes.Equal(serialized[hashesStart+HashSize:], rightHash[:]) {
t.Error("Right hash not found at expected position")
}
@ -116,7 +124,7 @@ func TestSerializeDeserializeStemNode(t *testing.T) {
}
// Serialize the node
serialized := s.serializeNode(ref)
serialized := s.serializeNode(ref, 8)
// Check the serialized format
if serialized[0] != nodeTypeStem {
@ -195,8 +203,9 @@ func TestDeserializeInvalidType(t *testing.T) {
// TestDeserializeInvalidLength tests deserialization with invalid data length.
func TestDeserializeInvalidLength(t *testing.T) {
s := newNodeStore()
// InternalNode with valid type byte but wrong length (needs exactly 65 bytes)
invalidData := []byte{nodeTypeInternal, 0, 0, 0}
// InternalNode group header with groupDepth=1 (valid) and a 1-byte bitmap
// announcing two present hashes, but the hash payload is missing.
invalidData := []byte{nodeTypeInternal, 1, 0xc0}
_, err := s.deserializeNode(invalidData, 0)
if err == nil {
@ -208,6 +217,21 @@ func TestDeserializeInvalidLength(t *testing.T) {
}
}
// TestDeserializeInvalidGroupDepth tests deserialization when the group depth
// byte is out of the supported 1..MaxGroupDepth range.
func TestDeserializeInvalidGroupDepth(t *testing.T) {
s := newNodeStore()
invalidData := []byte{nodeTypeInternal, 0, 0, 0}
_, err := s.deserializeNode(invalidData, 0)
if err == nil {
t.Fatal("Expected error for invalid group depth, got nil")
}
if err.Error() != "invalid group depth" {
t.Errorf("Expected 'invalid group depth' error, got: %v", err)
}
}
// TestKeyToPath tests the keyToPath function.
func TestKeyToPath(t *testing.T) {
tests := []struct {

View file

@ -95,7 +95,7 @@ func TestHashedNodeInsertValuesAtStem(t *testing.T) {
sn.setValue(byte(i), v)
}
}
serialized := rs.serializeNode(ref)
serialized := rs.serializeNode(ref, 8)
validResolver := func(path []byte, hash common.Hash) ([]byte, error) {
return serialized, nil

View file

@ -90,7 +90,7 @@ func TestInternalNodeGetWithResolver(t *testing.T) {
ref := rs.newStemRef(stem, 1)
sn := rs.getStem(ref.Index())
sn.setValue(5, common.HexToHash("0xabcd").Bytes())
return rs.serializeNode(ref), nil
return rs.serializeNode(ref, 8), nil
}
return nil, errors.New("node not found")
}
@ -290,10 +290,7 @@ func TestInternalNodeCollectNodes(t *testing.T) {
collectedPaths = append(collectedPaths, pathCopy)
}
err := s.collectNodes(s.root, []byte{1}, flushFn)
if err != nil {
t.Fatalf("Failed to collect nodes: %v", err)
}
s.collectNodes(s.root, []byte{1}, flushFn, 8)
// Should have collected 3 nodes: left stem, right stem, and the internal node itself
if len(collectedPaths) != 3 {

View file

@ -205,7 +205,7 @@ func (it *binaryNodeIterator) Path() []byte {
}
func (it *binaryNodeIterator) NodeBlob() []byte {
return it.store.serializeNode(it.current)
return it.store.serializeNode(it.current, it.trie.groupDepth)
}
// Leaf reports whether the iterator is currently positioned at a leaf value.

View file

@ -320,10 +320,7 @@ func TestStemNodeCollectNodes(t *testing.T) {
collectedPaths = append(collectedPaths, pathCopy)
}
err := s.collectNodes(s.root, []byte{0, 1, 0}, flushFn)
if err != nil {
t.Fatalf("Failed to collect nodes: %v", err)
}
s.collectNodes(s.root, []byte{0, 1, 0}, flushFn, 8)
// Should have collected one node (itself)
if len(collectedPaths) != 1 {

View file

@ -107,18 +107,83 @@ func (s *nodeStore) hashInternal(idx uint32) common.Hash {
return node.hash
}
// SerializeNode serializes a node into the flat on-disk format.
func (s *nodeStore) serializeNode(ref nodeRef) []byte {
// serializeSubtree recursively collects child hashes from a subtree of InternalNodes.
// It traverses up to `remainingDepth` levels, storing hashes of bottom-layer children.
// position tracks the current index (0 to 2^groupDepth - 1) for bitmap placement.
// hashes collects the hashes of present children, bitmap tracks which positions are present.
func (s *nodeStore) serializeSubtree(ref nodeRef, remainingDepth int, position int, absoluteDepth int, bitmap []byte, hashes *[]common.Hash) {
if remainingDepth == 0 {
// Bottom layer: store hash if not empty
switch ref.Kind() {
case kindEmpty:
// Leave bitmap bit unset, don't add hash
return
default:
// StemNode, HashedNode, or InternalNode at boundary: store hash
bitmap[position/8] |= 1 << (7 - (position % 8))
*hashes = append(*hashes, s.computeHash(ref))
}
return
}
switch ref.Kind() {
case kindInternal:
leftPos := position * 2
rightPos := position*2 + 1
s.serializeSubtree(s.getInternal(ref.Index()).left, remainingDepth-1, leftPos, absoluteDepth+1, bitmap, hashes)
s.serializeSubtree(s.getInternal(ref.Index()).right, remainingDepth-1, rightPos, absoluteDepth+1, bitmap, hashes)
case kindEmpty:
return
default:
// StemNode or HashedNode encountered before reaching the group's bottom
// layer. Compute the leaf bitmap position where this node's hash will
// be stored.
leafPos := position
switch ref.Kind() {
case kindStem:
sn := s.getStem(ref.Index())
// Extend position using the stem's key bits so that
// GetValuesAtStem traversal (which follows key bits) finds the hash.
for d := 0; d < remainingDepth; d++ {
bit := sn.Stem[(absoluteDepth+d)/8] >> (7 - ((absoluteDepth + d) % 8)) & 1
leafPos = leafPos*2 + int(bit)
}
default:
// HashedNode or unknown: extend all-left (no key bits available).
// This matches the all-zero path that resolveNode would follow.
leafPos = position << remainingDepth
}
bitmap[leafPos/8] |= 1 << (7 - (leafPos % 8))
*hashes = append(*hashes, s.computeHash(ref))
}
}
// SerializeNode serializes a node into the flat on-disk format.
func (s *nodeStore) serializeNode(ref nodeRef, groupDepth int) []byte {
switch ref.Kind() {
case kindInternal:
// InternalNode group: 1 byte type + 1 byte group depth + variable bitmap + N×32 byte hashes
bitmapSize := bitmapSizeForDepth(groupDepth)
bitmap := make([]byte, bitmapSize)
var hashes []common.Hash
node := s.getInternal(ref.Index())
var serialized [NodeTypeBytes + HashSize + HashSize]byte
s.serializeSubtree(ref, groupDepth, 0, int(node.depth), bitmap, &hashes)
// Build serialized output
serializedLen := NodeTypeBytes + 1 + bitmapSize + len(hashes)*HashSize
serialized := make([]byte, serializedLen)
serialized[0] = nodeTypeInternal
lh := s.computeHash(node.left)
rh := s.computeHash(node.right)
copy(serialized[NodeTypeBytes:NodeTypeBytes+HashSize], lh[:])
copy(serialized[NodeTypeBytes+HashSize:], rh[:])
return serialized[:]
serialized[1] = byte(groupDepth) // group depth => bitmap size for a sparse group
copy(serialized[2:2+bitmapSize], bitmap)
offset := NodeTypeBytes + 1 + bitmapSize
for _, h := range hashes {
copy(serialized[offset:offset+HashSize], h.Bytes())
offset += HashSize
}
return serialized
case kindStem:
sn := s.getStem(ref.Index())
@ -163,6 +228,59 @@ func (s *nodeStore) deserializeNodeWithHash(serialized []byte, depth int, hn com
return s.decodeNode(serialized, depth, hn, false, false)
}
// deserializeSubtree reconstructs an InternalNode subtree from grouped serialization.
// remainingDepth is how many more levels to build, position is current index in the bitmap,
// nodeDepth is the actual trie depth for the node being created.
// hashIdx tracks the current position in the hash data (incremented as hashes are consumed).
func (s *nodeStore) deserializeSubtree(hn common.Hash, remainingDepth int, position int, nodeDepth int, bitmap []byte, hashData []byte, hashIdx *int, mustRecompute bool, dirty bool) (nodeRef, error) {
if remainingDepth == 0 {
// Bottom layer: check bitmap and return HashedNode or Empty
if bitmap[position/8]>>(7-(position%8))&1 == 1 {
if len(hashData) < (*hashIdx+1)*HashSize {
return emptyRef, errInvalidSerializedLength
}
hash := common.BytesToHash(hashData[*hashIdx*HashSize : (*hashIdx+1)*HashSize])
*hashIdx++
return s.newHashedRef(hash), nil
}
return emptyRef, nil
}
// Check if this entire subtree is empty by examining all relevant bitmap bits
leftPos := position * 2
rightPos := position*2 + 1
// note that the parent might not need root computations, but the children
// do, because their hash isn't saved. Hence `mustRecompute` is set to `true`.
left, err := s.deserializeSubtree(common.Hash{}, remainingDepth-1, leftPos, nodeDepth+1, bitmap, hashData, hashIdx, true, dirty)
if err != nil {
return emptyRef, err
}
right, err := s.deserializeSubtree(common.Hash{}, remainingDepth-1, rightPos, nodeDepth+1, bitmap, hashData, hashIdx, true, dirty)
if err != nil {
return emptyRef, err
}
// If both children are empty, return Empty
if left.IsEmpty() && right.IsEmpty() {
return emptyRef, nil
}
ref := s.newInternalRef(nodeDepth)
node := s.getInternal(ref.Index())
node.left = left
node.right = right
node.mustRecompute = mustRecompute
if !mustRecompute {
// mustRecompute will only be false for the root of the subtree,
// for which we already know the hash.
node.hash = hn
node.mustRecompute = false
}
node.dirty = dirty
return ref, nil
}
func (s *nodeStore) decodeNode(serialized []byte, depth int, hn common.Hash, mustRecompute, dirty bool) (nodeRef, error) {
if len(serialized) == 0 {
return emptyRef, nil
@ -170,31 +288,23 @@ func (s *nodeStore) decodeNode(serialized []byte, depth int, hn common.Hash, mus
switch serialized[0] {
case nodeTypeInternal:
if len(serialized) != NodeTypeBytes+2*HashSize {
// Grouped format: 1 byte type + 1 byte group depth + variable bitmap + N×32 byte hashes
if len(serialized) < NodeTypeBytes+1 {
return emptyRef, errInvalidSerializedLength
}
var leftHash, rightHash common.Hash
copy(leftHash[:], serialized[NodeTypeBytes:NodeTypeBytes+HashSize])
copy(rightHash[:], serialized[NodeTypeBytes+HashSize:])
groupDepth := int(serialized[1])
if groupDepth < 1 || groupDepth > MaxGroupDepth {
return 0, errors.New("invalid group depth")
}
bitmapSize := bitmapSizeForDepth(groupDepth)
if len(serialized) < NodeTypeBytes+1+bitmapSize {
return 0, errInvalidSerializedLength
}
bitmap := serialized[2 : 2+bitmapSize]
hashData := serialized[2+bitmapSize:]
var leftRef, rightRef nodeRef
if leftHash != (common.Hash{}) {
leftRef = s.newHashedRef(leftHash)
}
if rightHash != (common.Hash{}) {
rightRef = s.newHashedRef(rightHash)
}
ref := s.newInternalRef(depth)
node := s.getInternal(ref.Index())
node.left = leftRef
node.right = rightRef
if !mustRecompute {
node.hash = hn
node.mustRecompute = false
}
node.dirty = dirty
return ref, nil
hashIdx := 0
return s.deserializeSubtree(hn, groupDepth, 0, depth, bitmap, hashData, &hashIdx, mustRecompute, dirty)
case nodeTypeStem:
if len(serialized) < NodeTypeBytes+StemSize+StemBitmapSize {
@ -230,45 +340,112 @@ func (s *nodeStore) decodeNode(serialized []byte, depth int, hn common.Hash, mus
// CollectNodes flushes every node that needs flushing via flushfn in post-order.
// Invariant: any ancestor of a node that needs flushing is itself marked, so a
// clean root means the whole subtree is clean.
func (s *nodeStore) collectNodes(ref nodeRef, path []byte, flushfn nodeFlushFn) error {
func (s *nodeStore) collectNodes(ref nodeRef, path []byte, flushfn nodeFlushFn, groupDepth int) {
switch ref.Kind() {
case kindEmpty:
return nil
case kindInternal:
node := s.getInternal(ref.Index())
if !node.dirty {
return nil
return
}
// Reuse path buffer across children: flushfn consumers
// (NodeSet.AddNode, tracer.Get) clone via string(path), so in-place
// mutation is safe.
path = append(path, 0)
if err := s.collectNodes(node.left, path, flushfn); err != nil {
return err
// Only flush at group boundaries (depth % groupDepth == 0)
if int(node.depth)%groupDepth == 0 {
// We're at a group boundary - first collect any nodes in deeper groups,
// then flush this group
s.collectChildGroups(node, path, flushfn, groupDepth, groupDepth-1)
flushfn(path, s.computeHash(ref), s.serializeNode(ref, groupDepth))
node.dirty = false
return
}
path[len(path)-1] = 1
if err := s.collectNodes(node.right, path, flushfn); err != nil {
return err
}
path = path[:len(path)-1]
flushfn(path, s.computeHash(ref), s.serializeNode(ref))
node.dirty = false
return nil
// Not at a group boundary - this shouldn't happen if we're called correctly from root
// but handle it by continuing to traverse
s.collectChildGroups(node, path, flushfn, groupDepth, groupDepth-(int(node.depth)%groupDepth)-1)
case kindStem:
sn := s.getStem(ref.Index())
if !sn.dirty {
return nil
return
}
flushfn(path, s.computeHash(ref), s.serializeNode(ref))
flushfn(path, s.computeHash(ref), s.serializeNode(ref, groupDepth))
sn.dirty = false
return nil
case kindHashed:
return nil // Already committed
case kindHashed, kindEmpty:
default:
return fmt.Errorf("CollectNodes: unexpected kind %d", ref.Kind())
panic(fmt.Sprintf("CollectNodes: unexpected kind %d", ref.Kind()))
}
}
// collectChildGroups traverses within a group to find and collect nodes in the next group.
// remainingLevels is how many more levels below the current node until we reach the group boundary.
// When remainingLevels=0, the current node's children are at the next group boundary.
func (s *nodeStore) collectChildGroups(node *InternalNode, path []byte, flushfn nodeFlushFn, groupDepth int, remainingLevels int) error {
if remainingLevels == 0 {
// Current node is at depth (groupBoundary - 1), its children are at the next group boundary
if !node.left.IsEmpty() {
s.collectNodes(node.left, appendBit(path, 0), flushfn, groupDepth)
}
if !node.right.IsEmpty() {
s.collectNodes(node.right, appendBit(path, 1), flushfn, groupDepth)
}
return nil
}
if !node.left.IsEmpty() {
switch node.left.Kind() {
case kindInternal:
n := s.getInternal(node.left.Index())
if err := s.collectChildGroups(n, appendBit(path, 0), flushfn, groupDepth, remainingLevels-1); err != nil {
return err
}
default:
extPath := s.extendPathToGroupLeaf(appendBit(path, 0), node.left, remainingLevels)
s.collectNodes(node.left, extPath, flushfn, groupDepth)
}
}
if !node.right.IsEmpty() {
switch node.right.Kind() {
case kindInternal:
n := s.getInternal(node.right.Index())
if err := s.collectChildGroups(n, appendBit(path, 1), flushfn, groupDepth, remainingLevels-1); err != nil {
return err
}
default:
extPath := s.extendPathToGroupLeaf(appendBit(path, 1), node.right, remainingLevels)
s.collectNodes(node.right, extPath, flushfn, groupDepth)
}
}
return nil
}
// extendPathToGroupLeaf extends a storage path to the group's leaf boundary,
// matching the projection done by serializeSubtree. For StemNodes, the path
// is extended using the stem's key bits (same as serializeSubtree). For other
// node types, the path is extended with all-zero (left) bits.
func (s *nodeStore) extendPathToGroupLeaf(path []byte, node nodeRef, remainingLevels int) []byte {
if remainingLevels <= 0 {
return path
}
if node.Kind() == kindStem {
sn := s.getStem(node.Index())
for _ = range remainingLevels {
bit := sn.Stem[len(path)/8] >> (7 - (len(path) % 8)) & 1
path = appendBit(path, bit)
}
} else {
// HashedNode or other: all-left extension (matches serializeSubtree's
// position << remainingDepth behavior).
for _ = range remainingLevels {
path = appendBit(path, 0)
}
}
return path
}
// appendBit appends a bit to a path, returning a new slice
func appendBit(path []byte, bit byte) []byte {
var p [256]byte
copy(p[:], path)
result := p[:len(path)]
return append(result, bit)
}
func (s *nodeStore) toDot(ref nodeRef, parent, path string) string {
switch ref.Kind() {
case kindInternal:

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