diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml
index efe76cf170..a3fa4a2ea7 100644
--- a/.gitea/workflows/release.yml
+++ b/.gitea/workflows/release.yml
@@ -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 }}
diff --git a/accounts/abi/bind/old.go b/accounts/abi/bind/old.go
index b09f5f3c7a..1fe1b1cca5 100644
--- a/accounts/abi/bind/old.go
+++ b/accounts/abi/bind/old.go
@@ -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
diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go
index 90713c03ca..90cfa68655 100644
--- a/accounts/abi/unpack_test.go
+++ b/accounts/abi/unpack_test.go
@@ -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")
}
}
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index aeafcfc838..0000000000
--- a/appveyor.yml
+++ /dev/null
@@ -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
diff --git a/beacon/engine/errors.go b/beacon/engine/errors.go
index 62773a0ea9..80e13b11b9 100644
--- a/beacon/engine/errors.go
+++ b/beacon/engine/errors.go
@@ -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}
diff --git a/beacon/engine/types.go b/beacon/engine/types.go
index a312fee88a..9b0b186df7 100644
--- a/beacon/engine/types.go
+++ b/beacon/engine/types.go
@@ -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()...)
}
diff --git a/build/checksums.txt b/build/checksums.txt
index 1832ce41dd..454efa93c4 100644
--- a/build/checksums.txt
+++ b/build/checksums.txt
@@ -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/
diff --git a/build/ci.go b/build/ci.go
index 173288bcdc..173a3280ce 100644
--- a/build/ci.go
+++ b/build/ci.go
@@ -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.
diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go
index c82358be49..d9d1fa02ac 100644
--- a/cmd/abigen/main.go
+++ b/cmd/abigen/main.go
@@ -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)
diff --git a/cmd/devp2p/enrcmd.go b/cmd/devp2p/enrcmd.go
index c9b692612f..af2cf90a81 100644
--- a/cmd/devp2p/enrcmd.go
+++ b/cmd/devp2p/enrcmd.go
@@ -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
diff --git a/cmd/era/main.go b/cmd/era/main.go
index 1c26f44ad4..43279e7001 100644
--- a/cmd/era/main.go
+++ b/cmd/era/main.go
@@ -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)
diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go
index 253ebe1111..15973e934d 100644
--- a/cmd/evm/internal/t8ntool/execution.go
+++ b/cmd/evm/internal/t8ntool/execution.go
@@ -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 {
diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go
index 7e068c06af..ca19ae3386 100644
--- a/cmd/evm/internal/t8ntool/transaction.go
+++ b/cmd/evm/internal/t8ntool/transaction.go
@@ -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)
diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go
index e0bb3a449d..89b703d3b8 100644
--- a/cmd/evm/internal/t8ntool/transition.go
+++ b/cmd/evm/internal/t8ntool/transition.go
@@ -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
}
diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go
index 82e7bdff3d..6d80056d04 100644
--- a/cmd/evm/runner.go
+++ b/cmd/evm/runner.go
@@ -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 {
diff --git a/cmd/geth/bintrie_convert.go b/cmd/geth/bintrie_convert.go
index 43d2e629ac..46cb3aa7e4 100644
--- a/cmd/geth/bintrie_convert.go
+++ b/cmd/geth/bintrie_convert.go
@@ -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)
}
diff --git a/cmd/geth/bintrie_convert_test.go b/cmd/geth/bintrie_convert_test.go
index 50ae752358..32e8c7e55b 100644
--- a/cmd/geth/bintrie_convert_test.go
+++ b/cmd/geth/bintrie_convert_test.go
@@ -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)
}
diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go
index 0aacb0878a..98ed348d8c 100644
--- a/cmd/geth/chaincmd.go
+++ b/cmd/geth/chaincmd.go
@@ -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)
}
diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index 8e2db32d76..c02e307bdc 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -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 {
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index ae869ec970..850e26d161 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -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()
- }
- }
- }()
- }
}
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 9d996f15cb..ea0f6f5ee4 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -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,
diff --git a/common/hexutil/json.go b/common/hexutil/json.go
index 6b9f412078..c00cd879c8 100644
--- a/common/hexutil/json.go
+++ b/common/hexutil/json.go
@@ -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())
diff --git a/core/bench_test.go b/core/bench_test.go
index 20d1a7794b..65179c54d4 100644
--- a/core/bench_test.go
+++ b/core/bench_test.go
@@ -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 {
diff --git a/core/bintrie_witness_test.go b/core/bintrie_witness_test.go
index 1b033151d3..5f6239e4fa 100644
--- a/core/bintrie_witness_test.go
+++ b/core/bintrie_witness_test.go
@@ -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))
diff --git a/core/blockchain.go b/core/blockchain.go
index cf2dcf2a1f..fcefd6bad6 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -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
diff --git a/core/chain_makers.go b/core/chain_makers.go
index 46cd98de61..7474d892b1 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -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
}
diff --git a/core/evm.go b/core/evm.go
index 818b23bee5..73e4c01a99 100644
--- a/core/evm.go
+++ b/core/evm.go
@@ -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
diff --git a/core/genesis.go b/core/genesis.go
index d77ea10d8c..6a0affa52e 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -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,
diff --git a/core/genesis_test.go b/core/genesis_test.go
index e15ad00222..94f1b3a4fd 100644
--- a/core/genesis_test.go
+++ b/core/genesis_test.go
@@ -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 := ¶ms.ChainConfig{
+func TestBinaryGenesisCommit(t *testing.T) {
+ var ubtTime uint64 = 0
+ ubtConfig := ¶ms.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) {
diff --git a/core/state/database_ubt.go b/core/state/database_ubt.go
index 718d93df87..16579f6d6a 100644
--- a/core/state/database_ubt.go
+++ b/core/state/database_ubt.go
@@ -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,
diff --git a/core/state/reader.go b/core/state/reader.go
index 5df0acbb9b..be07cec0f9 100644
--- a/core/state/reader.go
+++ b/core/state/reader.go
@@ -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
}
diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go
index a95bd66dde..8e473aa312 100644
--- a/core/state/snapshot/iterator_test.go
+++ b/core/state/snapshot/iterator_test.go
@@ -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 {
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 1858f4758d..e6d8b5bffc 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -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
}
diff --git a/core/state_processor.go b/core/state_processor.go
index 00b43aa149..6d7105c638 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -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: ¶ms.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: ¶ms.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))
diff --git a/core/state_transition.go b/core/state_transition.go
index c7b0593857..fcd483eeb7 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -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 {
diff --git a/core/state_transition_test.go b/core/state_transition_test.go
new file mode 100644
index 0000000000..8aab016123
--- /dev/null
+++ b/core/state_transition_test.go
@@ -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 .
+
+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)
+ }
+ })
+ }
+}
diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go
index 4030a0c339..f8021e00c4 100644
--- a/core/txpool/blobpool/blobpool.go
+++ b/core/txpool/blobpool/blobpool.go
@@ -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
diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go
index 7c57755401..8032e21e8a 100644
--- a/core/txpool/blobpool/blobpool_test.go
+++ b/core/txpool/blobpool/blobpool_test.go
@@ -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
diff --git a/core/txpool/blobpool/limbo.go b/core/txpool/blobpool/limbo.go
index 36284d6a03..b8bee2f22a 100644
--- a/core/txpool/blobpool/limbo.go
+++ b/core/txpool/blobpool/limbo.go
@@ -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 {
diff --git a/core/txpool/blobpool/metrics.go b/core/txpool/blobpool/metrics.go
index 52419ade09..44e2098b22 100644
--- a/core/txpool/blobpool/metrics.go
+++ b/core/txpool/blobpool/metrics.go
@@ -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
)
diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go
index 00630de04c..3d66803fd7 100644
--- a/core/txpool/legacypool/legacypool.go
+++ b/core/txpool/legacypool/legacypool.go
@@ -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))
diff --git a/core/txpool/locals/tx_tracker.go b/core/txpool/locals/tx_tracker.go
index bb178f175e..66f3248105 100644
--- a/core/txpool/locals/tx_tracker.go
+++ b/core/txpool/locals/tx_tracker.go
@@ -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
diff --git a/core/txpool/validation.go b/core/txpool/validation.go
index 6891dc94d2..c87bba31ac 100644
--- a/core/txpool/validation.go
+++ b/core/txpool/validation.go
@@ -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
}
diff --git a/crypto/signature_nocgo.go b/crypto/signature_nocgo.go
index 0aab7180d3..bf273612e9 100644
--- a/crypto/signature_nocgo.go
+++ b/crypto/signature_nocgo.go
@@ -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
diff --git a/eth/backend.go b/eth/backend.go
index 08a3c70c9d..af8b04bda6 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -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
}
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
index 8a4aced04b..1def169ae0 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -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)
diff --git a/eth/downloader/api.go b/eth/downloader/api.go
index 1fea35775e..6033e44474 100644
--- a/eth/downloader/api.go
+++ b/eth/downloader/api.go
@@ -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)
diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go
index 1de0933842..4a575d6856 100644
--- a/eth/downloader/downloader.go
+++ b/eth/downloader/downloader.go
@@ -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 {
diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go
index 9280d455fb..e6c477cd33 100644
--- a/eth/downloader/downloader_test.go
+++ b/eth/downloader/downloader_test.go
@@ -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 := ð.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")
+ }
+}
diff --git a/eth/downloader/events.go b/eth/downloader/events.go
index 25255a3a72..0fb380a857 100644
--- a/eth/downloader/events.go
+++ b/eth/downloader/events.go
@@ -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 }
diff --git a/eth/downloader/fetchers_concurrent.go b/eth/downloader/fetchers_concurrent.go
index 9d8cd114c1..51bf3404bd 100644
--- a/eth/downloader/fetchers_concurrent.go
+++ b/eth/downloader/fetchers_concurrent.go
@@ -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
+}
diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go
index c0cb9b174a..585906b8bd 100644
--- a/eth/downloader/queue.go
+++ b/eth/downloader/queue.go
@@ -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
diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go
index dd7436bf52..b51b78e199 100644
--- a/eth/ethconfig/config.go
+++ b/eth/ethconfig/config.go
@@ -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.
diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go
index ed85562f44..c5e45348be 100644
--- a/eth/ethconfig/gen_config.go
+++ b/eth/ethconfig/gen_config.go
@@ -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
}
diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go
index ace0752037..f45fc0d8c9 100644
--- a/eth/gasestimator/gasestimator.go
+++ b/eth/gasestimator/gasestimator.go
@@ -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)
diff --git a/eth/handler.go b/eth/handler.go
index 27b5e60697..76df635fb0 100644
--- a/eth/handler.go
+++ b/eth/handler.go
@@ -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
diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go
index a45abc90eb..d056d121d9 100644
--- a/eth/protocols/eth/handler_test.go
+++ b/eth/protocols/eth/handler_test.go
@@ -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 {
diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go
index 7556df9af2..3254a0abc2 100644
--- a/eth/protocols/eth/handlers.go
+++ b/eth/protocols/eth/handlers.go
@@ -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
diff --git a/eth/state_accessor.go b/eth/state_accessor.go
index f7ecff1488..b43396e393 100644
--- a/eth/state_accessor.go
+++ b/eth/state_accessor.go
@@ -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
}
diff --git a/eth/syncer/syncer.go b/eth/syncer/syncer.go
index c0d54b953b..b04d8f22e8 100644
--- a/eth/syncer/syncer.go
+++ b/eth/syncer/syncer.go
@@ -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.
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index dae11b81de..d9e40f7ec1 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -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)
diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go
index 749aade61b..31c3ebde93 100644
--- a/eth/tracers/logger/access_list_tracer.go
+++ b/eth/tracers/logger/access_list_tracer.go
@@ -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)
}
diff --git a/eth/tracers/logger/access_list_tracer_test.go b/eth/tracers/logger/access_list_tracer_test.go
new file mode 100644
index 0000000000..04b2b4b31b
--- /dev/null
+++ b/eth/tracers/logger/access_list_tracer_test.go
@@ -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 .
+
+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)
+ }
+}
diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go
index 7f2b2aecf2..8e445818ef 100644
--- a/eth/tracers/logger/logger.go
+++ b/eth/tracers/logger/logger.go
@@ -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)
}
diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go
index cec45a1e7a..a542eeffa2 100644
--- a/eth/tracers/native/4byte.go
+++ b/eth/tracers/native/4byte.go
@@ -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)
}
diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go
index 06220da84d..dfa804827b 100644
--- a/eth/tracers/native/call.go
+++ b/eth/tracers/native/call.go
@@ -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)
}
diff --git a/eth/tracers/native/call_flat.go b/eth/tracers/native/call_flat.go
index 4e7fc31a9c..484f2d4e3b 100644
--- a/eth/tracers/native/call_flat.go
+++ b/eth/tracers/native/call_flat.go
@@ -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.
diff --git a/eth/tracers/native/erc7562.go b/eth/tracers/native/erc7562.go
index 34e202f667..0bf80d77b5 100644
--- a/eth/tracers/native/erc7562.go
+++ b/eth/tracers/native/erc7562.go
@@ -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)
}
diff --git a/eth/tracers/native/mux.go b/eth/tracers/native/mux.go
index b7d6f29a6a..bb2101deec 100644
--- a/eth/tracers/native/mux.go
+++ b/eth/tracers/native/mux.go
@@ -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)
diff --git a/eth/tracers/native/mux_test.go b/eth/tracers/native/mux_test.go
new file mode 100644
index 0000000000..902b7a026a
--- /dev/null
+++ b/eth/tracers/native/mux_test.go
@@ -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 .
+
+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)
+ }
+}
diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go
index 36cb16e44b..7026cca7f3 100644
--- a/eth/tracers/native/prestate.go
+++ b/eth/tracers/native/prestate.go
@@ -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)
}
diff --git a/eth/tracers/native/tracer_test.go b/eth/tracers/native/tracer_test.go
new file mode 100644
index 0000000000..70e6283d34
--- /dev/null
+++ b/eth/tracers/native/tracer_test.go
@@ -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 .
+
+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()
+ })
+ }
+}
diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go
index 412f8955ba..1d8573f982 100644
--- a/ethclient/ethclient.go
+++ b/ethclient/ethclient.go
@@ -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
}
diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go
index f9e761e412..fb04d77669 100644
--- a/ethclient/ethclient_test.go
+++ b/ethclient/ethclient_test.go
@@ -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) {
diff --git a/ethclient/gen_simulate_call_result.go b/ethclient/gen_simulate_call_result.go
index 55e14cd697..18373bbb88 100644
--- a/ethclient/gen_simulate_call_result.go
+++ b/ethclient/gen_simulate_call_result.go
@@ -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)
}
diff --git a/ethclient/simulated/backend.go b/ethclient/simulated/backend.go
index d573c7e750..160ad924bf 100644
--- a/ethclient/simulated/backend.go
+++ b/ethclient/simulated/backend.go
@@ -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, ðConf)
diff --git a/internal/build/gotool.go b/internal/build/gotool.go
index 172fa13464..00aa9d6f02 100644
--- a/internal/build/gotool.go
+++ b/internal/build/gotool.go
@@ -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.
diff --git a/internal/download/download.go b/internal/download/download.go
index c59c8a90c3..94517166f5 100644
--- a/internal/download/download.go
+++ b/internal/download/download.go
@@ -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
}
diff --git a/internal/era/onedb/iterator.go b/internal/era/onedb/iterator.go
index b80fbabbc5..21dc5acbe0 100644
--- a/internal/era/onedb/iterator.go
+++ b/internal/era/onedb/iterator.go
@@ -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
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index e8669b86c6..6d38c6c7c8 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -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)
}
diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go
index 6cf52d636a..63e75bd3e3 100644
--- a/internal/ethapi/api_test.go
+++ b/internal/ethapi/api_test.go
@@ -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
diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go
index e3a14bf5d6..c34970578c 100644
--- a/internal/ethapi/simulate.go
+++ b/internal/ethapi/simulate.go
@@ -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}
diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go
index 4fb30e6289..1032d067f1 100644
--- a/internal/ethapi/transaction_args.go
+++ b/internal/ethapi/transaction_args.go
@@ -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,
diff --git a/miner/worker.go b/miner/worker.go
index 42e3695025..ccafa20b29 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -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
}
diff --git a/node/node.go b/node/node.go
index 01318881d4..56ecd7d522 100644
--- a/node/node.go
+++ b/node/node.go
@@ -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.
diff --git a/p2p/dial.go b/p2p/dial.go
index f9463d6d89..0ffcd10497 100644
--- a/p2p/dial.go
+++ b/p2p/dial.go
@@ -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())
}
diff --git a/p2p/discover/table.go b/p2p/discover/table.go
index 721cd7b589..016a2d1af3 100644
--- a/p2p/discover/table.go
+++ b/p2p/discover/table.go
@@ -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
}
diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go
index c3b71ea5a6..a16b4d9cab 100644
--- a/p2p/discover/table_test.go
+++ b/p2p/discover/table_test.go
@@ -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 {
diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go
index 9e824dae18..ae8cbec3e2 100644
--- a/p2p/discover/v4_udp.go
+++ b/p2p/discover/v4_udp.go
@@ -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:
}
}
diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go
index c62b513145..d8b6ef0674 100644
--- a/signer/core/signed_data.go
+++ b/signer/core/signed_data.go
@@ -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 {
diff --git a/tests/state_test.go b/tests/state_test.go
index 8444d211cf..cf1d4bce4c 100644
--- a/tests/state_test.go
+++ b/tests/state_test.go
@@ -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
diff --git a/tests/state_test_util.go b/tests/state_test_util.go
index f7cf1c0697..e33e15fc8c 100644
--- a/tests/state_test_util.go
+++ b/tests/state_test_util.go
@@ -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
diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go
index 8b8d0357bf..572c109f1e 100644
--- a/tests/transaction_test_util.go
+++ b/tests/transaction_test_util.go
@@ -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
}
diff --git a/trie/bintrie/binary_node.go b/trie/bintrie/binary_node.go
index e7f57d45a2..3516bf6bd5 100644
--- a/trie/bintrie/binary_node.go
+++ b/trie/bintrie/binary_node.go
@@ -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
diff --git a/trie/bintrie/binary_node_test.go b/trie/bintrie/binary_node_test.go
index 12ac199903..857060a0c0 100644
--- a/trie/bintrie/binary_node_test.go
+++ b/trie/bintrie/binary_node_test.go
@@ -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 {
diff --git a/trie/bintrie/hashed_node_test.go b/trie/bintrie/hashed_node_test.go
index ae77b7c570..2e12bfba5e 100644
--- a/trie/bintrie/hashed_node_test.go
+++ b/trie/bintrie/hashed_node_test.go
@@ -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
diff --git a/trie/bintrie/internal_node_test.go b/trie/bintrie/internal_node_test.go
index 8d5a75de8c..4d8da8af37 100644
--- a/trie/bintrie/internal_node_test.go
+++ b/trie/bintrie/internal_node_test.go
@@ -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 {
diff --git a/trie/bintrie/iterator.go b/trie/bintrie/iterator.go
index 31645430c3..a920f91378 100644
--- a/trie/bintrie/iterator.go
+++ b/trie/bintrie/iterator.go
@@ -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.
diff --git a/trie/bintrie/stem_node_test.go b/trie/bintrie/stem_node_test.go
index 5faf903fba..ae6b57ab34 100644
--- a/trie/bintrie/stem_node_test.go
+++ b/trie/bintrie/stem_node_test.go
@@ -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 {
diff --git a/trie/bintrie/store_commit.go b/trie/bintrie/store_commit.go
index 7101087b51..b14bffbc6c 100644
--- a/trie/bintrie/store_commit.go
+++ b/trie/bintrie/store_commit.go
@@ -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:
diff --git a/trie/bintrie/trie.go b/trie/bintrie/trie.go
index 8c69e0aa00..e3436e3df1 100644
--- a/trie/bintrie/trie.go
+++ b/trie/bintrie/trie.go
@@ -107,9 +107,14 @@ func ChunkifyCode(code []byte) ChunkedCode {
// BinaryTrie is the implementation of https://eips.ethereum.org/EIPS/eip-7864.
type BinaryTrie struct {
- store *nodeStore
- reader *trie.Reader
- tracer *trie.PrevalueTracer
+ store *nodeStore
+ reader *trie.Reader
+ tracer *trie.PrevalueTracer
+ groupDepth int // Number of levels per serialized group (1-8, default 8)
+}
+
+func (t *BinaryTrie) GroupDepth() int {
+ return t.groupDepth
}
// ToDot converts the binary trie to a DOT language representation. Useful for debugging.
@@ -119,15 +124,20 @@ func (t *BinaryTrie) ToDot() string {
}
// NewBinaryTrie creates a new binary trie.
-func NewBinaryTrie(root common.Hash, db database.NodeDatabase) (*BinaryTrie, error) {
+// groupDepth specifies the number of levels per serialized group (1-8).
+func NewBinaryTrie(root common.Hash, db database.NodeDatabase, groupDepth int) (*BinaryTrie, error) {
+ if groupDepth < 1 || groupDepth > MaxGroupDepth {
+ panic("invalid group depth size")
+ }
reader, err := trie.NewReader(root, common.Hash{}, db)
if err != nil {
return nil, err
}
t := &BinaryTrie{
- store: newNodeStore(),
- reader: reader,
- tracer: trie.NewPrevalueTracer(),
+ store: newNodeStore(),
+ reader: reader,
+ tracer: trie.NewPrevalueTracer(),
+ groupDepth: groupDepth,
}
// Parse the root node if it's not empty
if root != types.EmptyBinaryHash && root != types.EmptyRootHash {
@@ -312,12 +322,9 @@ func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) {
// Pre-size the path buffer: collectNodes reuses it in-place via
// append/truncate; 32 covers typical binary-trie depth without regrowth.
pathBuf := make([]byte, 0, 32)
- err := t.store.collectNodes(t.store.root, pathBuf, func(path []byte, hash common.Hash, serialized []byte) {
+ t.store.collectNodes(t.store.root, pathBuf, func(path []byte, hash common.Hash, serialized []byte) {
nodeset.AddNode(path, trienode.NewNodeWithPrev(hash, serialized, t.tracer.Get(path)))
- })
- if err != nil {
- panic(fmt.Errorf("CollectNodes failed: %v", err))
- }
+ }, t.groupDepth)
return t.Hash(), nodeset
}
@@ -341,9 +348,10 @@ func (t *BinaryTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
// Copy creates a deep copy of the trie.
func (t *BinaryTrie) Copy() *BinaryTrie {
return &BinaryTrie{
- store: t.store.Copy(),
- reader: t.reader,
- tracer: t.tracer.Copy(),
+ store: t.store.Copy(),
+ reader: t.reader,
+ tracer: t.tracer.Copy(),
+ groupDepth: t.groupDepth,
}
}
diff --git a/trie/bintrie/trie_test.go b/trie/bintrie/trie_test.go
index 73aacb76c4..8b7d9e46d6 100644
--- a/trie/bintrie/trie_test.go
+++ b/trie/bintrie/trie_test.go
@@ -768,8 +768,9 @@ func TestGetStorageNonMembershipInternalRoot(t *testing.T) {
// flushes only the root-to-leaf path.
func TestCommitSkipCleanSubtrees(t *testing.T) {
tr := &BinaryTrie{
- store: newNodeStore(),
- tracer: trie.NewPrevalueTracer(),
+ store: newNodeStore(),
+ tracer: trie.NewPrevalueTracer(),
+ groupDepth: 1,
}
const n = 200
key := func(i int) [HashSize]byte {
diff --git a/triedb/database.go b/triedb/database.go
index 533097c9e3..ef95169df1 100644
--- a/triedb/database.go
+++ b/triedb/database.go
@@ -31,12 +31,15 @@ import (
// Config defines all necessary options for database.
type Config struct {
- Preimages bool // Flag whether the preimage of node key is recorded
- IsUBT bool // Flag whether the db is holding a verkle tree
- HashDB *hashdb.Config // Configs for hash-based scheme
- PathDB *pathdb.Config // Configs for experimental path-based scheme
+ Preimages bool // Flag whether the preimage of node key is recorded
+ IsUBT bool // Flag whether the db is holding a unified binary tree
+ BinTrieGroupDepth int // Number of levels per serialized group in binary trie (1-8, default 8)
+ HashDB *hashdb.Config // Configs for hash-based scheme
+ PathDB *pathdb.Config // Configs for experimental path-based scheme
}
+const DefaultBinTrieGroupDepth = 5
+
// HashDefaults represents a config for using hash-based scheme with
// default settings.
var HashDefaults = &Config{
@@ -45,12 +48,13 @@ var HashDefaults = &Config{
HashDB: hashdb.Defaults,
}
-// UBTDefaults represents a config for holding verkle trie data
+// UBTDefaults represents a config for holding unified binary trie data
// using path-based scheme with default settings.
var UBTDefaults = &Config{
- Preimages: false,
- IsUBT: true,
- PathDB: pathdb.Defaults,
+ Preimages: false,
+ IsUBT: true,
+ BinTrieGroupDepth: DefaultBinTrieGroupDepth,
+ PathDB: pathdb.Defaults,
}
// backend defines the methods needed to access/update trie nodes in different
@@ -323,6 +327,16 @@ func (db *Database) Enable(root common.Hash) error {
return pdb.Enable(root)
}
+// AdoptSyncedState activates the database after a snap/2 sync and adopts the
+// flat state populated during sync as-is, skipping regeneration.
+func (db *Database) AdoptSyncedState(root common.Hash) error {
+ pdb, ok := db.backend.(*pathdb.Database)
+ if !ok {
+ return errors.New("not supported")
+ }
+ return pdb.AdoptSyncedState(root)
+}
+
// Journal commits an entire diff hierarchy to disk into a single journal entry.
// This is meant to be used during shutdown to persist the snapshot without
// flattening everything down (bad for reorgs). It's only supported by path-based
@@ -393,3 +407,7 @@ func (db *Database) SnapshotCompleted() bool {
}
return pdb.SnapshotCompleted()
}
+
+func (db *Database) BinTrieGroupDepth() int {
+ return db.config.BinTrieGroupDepth
+}
diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go
index 98975d7fa5..e52949c93e 100644
--- a/triedb/pathdb/database.go
+++ b/triedb/pathdb/database.go
@@ -365,16 +365,9 @@ func (db *Database) Disable() error {
return nil
}
-// Enable activates database and resets the state tree with the provided persistent
-// state root once the state sync is finished.
-func (db *Database) Enable(root common.Hash) error {
- db.lock.Lock()
- defer db.lock.Unlock()
-
- // Short circuit if the database is in read only mode.
- if db.readOnly {
- return errDatabaseReadOnly
- }
+// resetForReactivation performs the pathdb-side bookkeeping shared by both
+// Enable and AdoptSyncedState.
+func (db *Database) resetForReactivation(root common.Hash) error {
// Ensure the provided state root matches the stored one.
stored, err := db.hasher(rawdb.ReadAccountTrieNode(db.diskdb, nil))
if err != nil {
@@ -383,27 +376,40 @@ func (db *Database) Enable(root common.Hash) error {
if stored != root {
return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root)
}
- // Drop the stale state journal in persistent database and
- // reset the persistent state id back to zero.
+ // Drop the stale state journal marker and reset the persistent state id
+ // back to zero.
batch := db.diskdb.NewBatch()
rawdb.DeleteSnapshotRoot(batch)
rawdb.WritePersistentStateID(batch, 0)
if err := batch.Write(); err != nil {
return err
}
- // Clean up all state histories in freezer. Theoretically
- // all root->id mappings should be removed as well. Since
- // mappings can be huge and might take a while to clear
- // them, just leave them in disk and wait for overwriting.
+ // Clean up all state histories in the freezer. Theoretically all root->id
+ // mappings should be removed as well; since those can be huge, leave them
+ // on disk and let them be overwritten.
purgeHistory(db.stateFreezer, db.diskdb, typeStateHistory)
purgeHistory(db.trienodeFreezer, db.diskdb, typeTrienodeHistory)
- // Re-enable the database as the final step.
+ // Re-enable the database as the final bookkeeping step.
db.waitSync = false
rawdb.WriteSnapSyncStatusFlag(db.diskdb, rawdb.StateSyncFinished)
+ return nil
+}
- // Re-construct a new disk layer backed by persistent state
- // and schedule the state snapshot generation if it's permitted.
+// Enable activates the database after a snap/1 sync and schedules background
+// regeneration of the snapshot from the trie.
+func (db *Database) Enable(root common.Hash) error {
+ db.lock.Lock()
+ defer db.lock.Unlock()
+
+ if db.readOnly {
+ return errDatabaseReadOnly
+ }
+ if err := db.resetForReactivation(root); err != nil {
+ return err
+ }
+ // Re-construct a new disk layer backed by persistent state and schedule
+ // the state snapshot generation if it's permitted.
db.tree.init(generateSnapshot(db, root, db.isUBT || db.config.SnapshotNoBuild))
// After snap sync, the state of the database may have changed completely.
@@ -416,6 +422,43 @@ func (db *Database) Enable(root common.Hash) error {
return nil
}
+// AdoptSyncedState reactivates the database after a snap/2 sync. The syncer
+// already wrote a consistent flat state, so we take it as-is instead of
+// rebuilding it from the trie. The new disk layer has no generator attached,
+// and a "done" marker is written so future boots know the snapshot is
+// already complete.
+func (db *Database) AdoptSyncedState(root common.Hash) error {
+ db.lock.Lock()
+ defer db.lock.Unlock()
+
+ if db.readOnly {
+ return errDatabaseReadOnly
+ }
+ if err := db.resetForReactivation(root); err != nil {
+ return err
+ }
+
+ // Tell the snapshot subsystem the flat state is good by writing the new root
+ // and a "done" marker (nil journal) so the next boot doesn't try to rebuild it.
+ batch := db.diskdb.NewBatch()
+ rawdb.WriteSnapshotRoot(batch, root)
+ journalProgress(batch, nil, nil)
+ if err := batch.Write(); err != nil {
+ return err
+ }
+
+ // New disk layer, no generator attached. Nothing to rebuild, and reads
+ // can serve the flat state right away without waiting on a generator to
+ // scan past every key.
+ dl := newDiskLayer(root, 0, db, nil, nil, newBuffer(db.config.WriteBufferSize, nil, nil, 0), nil)
+ db.tree.init(dl)
+
+ db.setHistoryIndexer()
+
+ log.Info("Adopted synced state", "root", root)
+ return nil
+}
+
// Recover rollbacks the database to a specified historical point.
// The state is supported as the rollback destination only if it's
// canonical state and the corresponding trie histories are existent.
diff --git a/triedb/pathdb/database_test.go b/triedb/pathdb/database_test.go
index 8ceb22eaba..41212dc9d0 100644
--- a/triedb/pathdb/database_test.go
+++ b/triedb/pathdb/database_test.go
@@ -748,6 +748,84 @@ func TestDisable(t *testing.T) {
}
}
+// TestAdoptSyncedState verifies that AdoptSyncedState rejects a wrong root,
+// writes the on-disk markers that say the snapshot is already complete,
+// leaves a single fresh disk layer with no generator attached, and clears
+// out stale state histories.
+func TestAdoptSyncedState(t *testing.T) {
+ maxDiffLayers = 4
+ defer func() {
+ maxDiffLayers = 128
+ }()
+
+ tester := newTester(t, &testerConfig{layers: 12})
+ defer tester.release()
+
+ // Push everything down to disk so the trie root is the persistent root.
+ if err := tester.db.Commit(tester.lastHash(), false); err != nil {
+ t.Fatalf("Failed to commit, err: %v", err)
+ }
+ stored := crypto.Keccak256Hash(rawdb.ReadAccountTrieNode(tester.db.diskdb, nil))
+
+ // Mimic the snap-syncing state.
+ if err := tester.db.Disable(); err != nil {
+ t.Fatalf("Failed to disable database: %v", err)
+ }
+ // Mismatched root must be rejected.
+ if err := tester.db.AdoptSyncedState(types.EmptyRootHash); err == nil {
+ t.Fatal("Mismatched root should be rejected")
+ }
+ if err := tester.db.AdoptSyncedState(stored); err != nil {
+ t.Fatalf("AdoptSyncedState failed: %v", err)
+ }
+
+ // On-disk markers reflect a completed snapshot.
+ if got := rawdb.ReadSnapshotRoot(tester.db.diskdb); got != stored {
+ t.Fatalf("SnapshotRoot mismatch: got %x want %x", got, stored)
+ }
+ if blob := rawdb.ReadSnapshotGenerator(tester.db.diskdb); len(blob) == 0 {
+ t.Fatal("Generator journal not written")
+ } else {
+ var entry journalGenerator
+ if err := rlp.DecodeBytes(blob, &entry); err != nil {
+ t.Fatalf("Failed to decode generator journal: %v", err)
+ }
+ if !entry.Done {
+ t.Fatal("Generator journal should be marked Done")
+ }
+ // RLP turns a nil slice into an empty one on decode, so check length.
+ if len(entry.Marker) != 0 {
+ t.Fatalf("Generator marker should be empty, got %x", entry.Marker)
+ }
+ }
+ if rawdb.ReadSnapSyncStatusFlag(tester.db.diskdb) != rawdb.StateSyncFinished {
+ t.Fatal("Sync-status flag should be StateSyncFinished")
+ }
+ if tester.db.waitSync {
+ t.Fatal("waitSync should be false after adopt")
+ }
+
+ // State histories are purged.
+ if n, err := tester.db.stateFreezer.Ancients(); err != nil || n != 0 {
+ t.Fatalf("State histories not purged: count=%d err=%v", n, err)
+ }
+
+ // Layer tree has a single disk layer with no generator attached.
+ if got := tester.db.tree.len(); got != 1 {
+ t.Fatalf("Expected single layer, got %d", got)
+ }
+ dl := tester.db.tree.bottom()
+ if dl.rootHash() != stored {
+ t.Fatalf("Disk layer root mismatch: got %x want %x", dl.rootHash(), stored)
+ }
+ if dl.generator != nil {
+ t.Fatal("Disk layer should have no generator after adopt")
+ }
+ if dl.genMarker() != nil {
+ t.Fatal("genMarker should be nil after adopt")
+ }
+}
+
func TestCommit(t *testing.T) {
// Redefine the diff layer depth allowance for faster testing.
maxDiffLayers = 4
diff --git a/triedb/pathdb/iterator_test.go b/triedb/pathdb/iterator_test.go
index adb534f47d..191c2fadf5 100644
--- a/triedb/pathdb/iterator_test.go
+++ b/triedb/pathdb/iterator_test.go
@@ -369,7 +369,7 @@ func TestAccountIteratorTraversalValues(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 {
@@ -489,7 +489,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 {
diff --git a/triedb/pathdb/journal.go b/triedb/pathdb/journal.go
index efcc3f2549..657fbbff27 100644
--- a/triedb/pathdb/journal.go
+++ b/triedb/pathdb/journal.go
@@ -161,7 +161,19 @@ func loadGenerator(db ethdb.KeyValueReader, hash nodeHasher) (*journalGenerator,
// loadLayers loads a pre-existing state layer backed by a key-value store.
func (db *Database) loadLayers() layer {
// Retrieve the root node of persistent state.
- root, err := db.hasher(rawdb.ReadAccountTrieNode(db.diskdb, nil))
+ var (
+ root common.Hash
+ err error
+ )
+ if db.isUBT {
+ root = rawdb.ReadSnapshotRoot(db.diskdb)
+ if root == (common.Hash{}) {
+ root = types.EmptyBinaryHash
+ }
+ } else {
+ blob := rawdb.ReadAccountTrieNode(db.diskdb, nil)
+ root, err = db.hasher(blob)
+ }
if err != nil {
log.Crit("Failed to compute node hash", "err", err)
}
diff --git a/version/version.go b/version/version.go
index ea1f5fc632..5d402f3009 100644
--- a/version/version.go
+++ b/version/version.go
@@ -19,6 +19,6 @@ package version
const (
Major = 1 // Major version component of the current release
Minor = 17 // Minor version component of the current release
- Patch = 3 // Patch version component of the current release
+ Patch = 4 // Patch version component of the current release
Meta = "unstable" // Version metadata to append to the version string
)