resolve conflict

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

View file

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

View file

@ -176,6 +176,13 @@ var (
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves // ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
// an empty contract behind. // an empty contract behind.
ErrNoCodeAfterDeploy = bind2.ErrNoCodeAfterDeploy 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 // ContractCaller defines the methods needed to allow operating with a contract on a read

View file

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

View file

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

View file

@ -81,6 +81,7 @@ var (
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"} TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"} InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"} 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_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil} STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}

View file

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

View file

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

View file

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

View file

@ -215,7 +215,7 @@ func generate(c *cli.Context) error {
code string code string
err error 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) code, err = abigen.BindV2(types, abis, bins, c.String(pkgFlag.Name), libs, aliases)
} else { } else {
code, err = abigen.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases) code, err = abigen.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases)

View file

@ -194,7 +194,7 @@ func formatAttrString(v rlp.RawValue) (string, bool) {
func formatAttrIP(v rlp.RawValue) (string, bool) { func formatAttrIP(v rlp.RawValue) (string, bool) {
content, _, err := rlp.SplitString(v) 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 "", false
} }
return net.IP(content).String(), true return net.IP(content).String(), true

View file

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

View file

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

View file

@ -133,7 +133,7 @@ func Transaction(ctx *cli.Context) error {
} }
// Check intrinsic gas // Check intrinsic gas
rules := chainConfig.Rules(common.Big0, true, 0) 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 { if err != nil {
r.Error = err r.Error = err
results = append(results, r) results = append(results, r)
@ -147,7 +147,7 @@ func Transaction(ctx *cli.Context) error {
} }
// For Prague txs, validate the floor data gas. // For Prague txs, validate the floor data gas.
if rules.IsPrague { if rules.IsPrague {
floorDataGas, err := core.FloorDataGas(rules, tx.Data()) floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
if err != nil { if err != nil {
r.Error = err r.Error = err
results = append(results, r) results = append(results, r)

View file

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

View file

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

View file

@ -151,7 +151,7 @@ func convertToBinaryTrie(ctx *cli.Context) error {
}) })
defer destTriedb.Close() 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 { if err != nil {
return fmt.Errorf("failed to create binary trie: %w", err) 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() runtime.GC()
debug.FreeOSMemory() debug.FreeOSMemory()
bt, err := bintrie.NewBinaryTrie(newRoot, destDB) bt, err := bintrie.NewBinaryTrie(newRoot, destDB, bt.GroupDepth())
if err != nil { if err != nil {
return nil, common.Hash{}, fmt.Errorf("failed to reload binary trie: %w", err) return nil, common.Hash{}, fmt.Errorf("failed to reload binary trie: %w", err)
} }

View file

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

View file

@ -325,7 +325,7 @@ func dumpGenesis(ctx *cli.Context) error {
var genesis *core.Genesis var genesis *core.Genesis
if utils.IsNetworkPreset(ctx) { if utils.IsNetworkPreset(ctx) {
genesis = utils.MakeGenesis(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) genesis = core.DeveloperGenesisBlock(11_500_000, nil)
} }

View file

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

View file

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

View file

@ -297,6 +297,12 @@ var (
Value: ethconfig.Defaults.EnableStateSizeTracking, Value: ethconfig.Defaults.EnableStateSizeTracking,
Category: flags.StateCategory, 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{ StateHistoryFlag = &cli.Uint64Flag{
Name: "history.state", 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)", 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) { if ctx.IsSet(TrienodeHistoryFullValueCheckpointFlag.Name) {
cfg.NodeFullValueCheckpoint = uint32(ctx.Uint(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) { if ctx.IsSet(StateSchemeFlag.Name) {
cfg.StateScheme = ctx.String(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. // RegisterSyncOverrideService adds the synchronization override service into node.
func RegisterSyncOverrideService(stack *node.Node, eth *eth.Ethereum, target common.Hash, exitWhenSynced bool) { func RegisterSyncOverrideService(stack *node.Node, eth *eth.Ethereum, config syncer.Config) {
if target != (common.Hash{}) { if config.TargetBlock != (common.Hash{}) {
log.Info("Registered sync override service", "hash", target, "exitWhenSynced", exitWhenSynced) log.Info("Registered sync override service", "hash", config.TargetBlock, "exitWhenSynced", config.ExitWhenSynced)
} else { } else {
log.Info("Registered sync override service") log.Info("Registered sync override service")
} }
syncer.Register(stack, eth, target, exitWhenSynced) syncer.Register(stack, eth, config)
} }
// SetupMetrics configures the metrics system. // 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), StateHistory: ctx.Uint64(StateHistoryFlag.Name),
TrienodeHistory: ctx.Int64(TrienodeHistoryFlag.Name), TrienodeHistory: ctx.Int64(TrienodeHistoryFlag.Name),
NodeFullValueCheckpoint: uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name)), NodeFullValueCheckpoint: uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name)),
BinTrieGroupDepth: ctx.Int(BinTrieGroupDepthFlag.Name),
// Disable transaction indexing/unindexing. // Disable transaction indexing/unindexing.
TxLookupLimit: -1, TxLookupLimit: -1,

View file

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

View file

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

View file

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

View file

@ -170,9 +170,10 @@ type BlockChainConfig struct {
TrieNoAsyncFlush bool // Whether the asynchronous buffer flushing is disallowed TrieNoAsyncFlush bool // Whether the asynchronous buffer flushing is disallowed
TrieJournalDirectory string // Directory path to the journal used for persisting trie data across node restarts 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 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 StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top
ArchiveMode bool // Whether to enable the archive mode 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. // 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; // 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. // triedbConfig derives the configures for trie database.
func (cfg *BlockChainConfig) triedbConfig(isUBT bool) *triedb.Config { func (cfg *BlockChainConfig) triedbConfig(isUBT bool) *triedb.Config {
config := &triedb.Config{ config := &triedb.Config{
Preimages: cfg.Preimages, Preimages: cfg.Preimages,
IsUBT: isUBT, IsUBT: isUBT,
BinTrieGroupDepth: cfg.BinTrieGroupDepth,
} }
if cfg.StateScheme == rawdb.HashScheme { if cfg.StateScheme == rawdb.HashScheme {
config.HashDB = &hashdb.Config{ 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. // If all checks out, manually set the head block.
rawdb.WriteHeadBlockHash(bc.db, hash)
bc.currentBlock.Store(block.Header()) bc.currentBlock.Store(block.Header())
headBlockGauge.Update(int64(block.NumberU64())) 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))) blockReorgAddMeter.Mark(int64(len(newChain)))
} else { } else {
// len(newChain) == 0 && len(oldChain) > 0 // len(newChain) == 0 && len(oldChain) > 0
// rewind the canonical chain to a lower point. // Rewind the canonical chain to a lower point. In EPBs we can reorg to
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)) // 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 // Acquire the tx-lookup lock before mutation. This step is essential
// as the txlookups should be changed atomically, and all subsequent // as the txlookups should be changed atomically, and all subsequent

View file

@ -17,6 +17,7 @@
package core package core
import ( import (
"context"
"fmt" "fmt"
"math/big" "math/big"
@ -314,28 +315,17 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) {
// off the statedb before executing the system calls. // off the statedb before executing the system calls.
statedb = statedb.Copy() 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, err := PostExecution(context.Background(), b.cm.config, b.header.Number, b.header.Time, blockLogs, evm)
requests = [][]byte{} if err != nil {
// EIP-6110 deposits panic(fmt.Sprintf("failed to run post-execution: %v", err))
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))
}
} }
return requests return requests
} }

View file

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

View file

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

View file

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

View file

@ -96,7 +96,7 @@ func (db *UBTDatabase) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Rea
// OpenTrie opens the main account trie at a specific root hash. // OpenTrie opens the main account trie at a specific root hash.
func (db *UBTDatabase) OpenTrie(root common.Hash) (Trie, error) { 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, // OpenStorageTrie opens the storage trie of an account. In binary trie mode,

View file

@ -255,7 +255,7 @@ type ubtTrieReader struct {
// newUBTTrieReader constructs a Unified-binary-trie reader of the specific state. // 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. // 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) { 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 { if binErr != nil {
return nil, binErr return nil, binErr
} }

View file

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

View file

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

View file

@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/internal/telemetry" "github.com/ethereum/go-ethereum/internal/telemetry"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256"
) )
// StateProcessor is a basic Processor, which takes care of transitioning // 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 { if hooks := cfg.Tracer; hooks != nil {
tracingStateDB = state.NewHookedState(statedb, hooks) tracingStateDB = state.NewHookedState(statedb, hooks)
} }
// Mutate the block and state according to any hard-fork specs // Mutate the block and state according to any hard-fork specs
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 { if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 {
misc.ApplyDAOHardFork(tracingStateDB) misc.ApplyDAOHardFork(tracingStateDB)
} }
var ( var (
context vm.BlockContext context = NewEVMBlockContext(header, p.chain, nil)
signer = types.MakeSigner(config, header.Number, header.Time) 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() defer evm.Release()
if jumpDestCache != nil { if jumpDestCache != nil {
evm.SetJumpDestCache(jumpDestCache) evm.SetJumpDestCache(jumpDestCache)
} }
// Run the pre-execution system calls
if beaconRoot := block.BeaconRoot(); beaconRoot != nil { PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time())
ProcessBeaconBlockRoot(*beaconRoot, evm)
}
if config.IsPrague(block.Number(), block.Time()) || config.IsUBT(block.Number(), block.Time()) {
ProcessParentBlockHash(block.ParentHash(), evm)
}
// Iterate over and process the individual transactions // Iterate over and process the individual transactions
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
msg, err := TransactionToMessage(tx, signer, header.BaseFee) 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...) allLogs = append(allLogs, receipt.Logs...)
spanEnd(nil) 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 { if err != nil {
return nil, err return nil, err
} }
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards) // Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body()) 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 }, nil
} }
// postExecution processes the post-execution system calls if Prague is enabled. // PreExecution processes pre-execution system calls.
func postExecution(ctx context.Context, config *params.ChainConfig, block *types.Block, allLogs []*types.Log, evm *vm.EVM) (requests [][]byte, err error) { 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") _, _, spanEnd := telemetry.StartSpan(ctx, "core.postExecution")
defer spanEnd(&err) defer spanEnd(&err)
// Read requests if Prague is enabled. // Read requests if Prague is enabled.
if config.IsPrague(block.Number(), block.Time()) { if config.IsPrague(number, time) {
requests = [][]byte{} requests = [][]byte{}
// EIP-6110 // EIP-6110
if err := ParseDepositLogs(&requests, allLogs, config); err != nil { 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 // EIP-7002
if err := ProcessWithdrawalQueue(&requests, evm); err != nil { 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 // EIP-7251
if err := ProcessConsolidationQueue(&requests, evm); err != nil { 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 return requests, nil
} }
@ -257,9 +264,9 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
msg := &Message{ msg := &Message{
From: params.SystemAddress, From: params.SystemAddress,
GasLimit: 30_000_000, GasLimit: 30_000_000,
GasPrice: common.Big0, GasPrice: uint256.NewInt(0),
GasFeeCap: common.Big0, GasFeeCap: uint256.NewInt(0),
GasTipCap: common.Big0, GasTipCap: uint256.NewInt(0),
To: &params.BeaconRootsAddress, To: &params.BeaconRootsAddress,
Data: beaconRoot[:], Data: beaconRoot[:],
} }
@ -284,9 +291,9 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
msg := &Message{ msg := &Message{
From: params.SystemAddress, From: params.SystemAddress,
GasLimit: 30_000_000, GasLimit: 30_000_000,
GasPrice: common.Big0, GasPrice: uint256.NewInt(0),
GasFeeCap: common.Big0, GasFeeCap: uint256.NewInt(0),
GasTipCap: common.Big0, GasTipCap: uint256.NewInt(0),
To: &params.HistoryStorageAddress, To: &params.HistoryStorageAddress,
Data: prevHash.Bytes(), Data: prevHash.Bytes(),
} }
@ -324,9 +331,9 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
msg := &Message{ msg := &Message{
From: params.SystemAddress, From: params.SystemAddress,
GasLimit: 30_000_000, GasLimit: 30_000_000,
GasPrice: common.Big0, GasPrice: uint256.NewInt(0),
GasFeeCap: common.Big0, GasFeeCap: uint256.NewInt(0),
GasTipCap: common.Big0, GasTipCap: uint256.NewInt(0),
To: &addr, To: &addr,
} }
evm.SetTxContext(NewEVMTxContext(msg)) evm.SetTxContext(NewEVMTxContext(msg))

View file

@ -68,7 +68,7 @@ func (result *ExecutionResult) Revert() []byte {
} }
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data. // 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 // Set the starting gas for the raw transaction
var gas uint64 var gas uint64
if isContractCreation && isHomestead { if isContractCreation && isHomestead {
@ -107,8 +107,32 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
} }
} }
if accessList != nil { if accessList != nil {
gas += uint64(len(accessList)) * params.TxAccessListAddressGas addresses := uint64(len(accessList))
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas 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 { if authList != nil {
gas += uint64(len(authList)) * params.CallNewAccountGas 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). // 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 ( var (
tokens uint64 tokens uint64
tokenCost uint64 tokenCost uint64
@ -125,15 +149,41 @@ func FloorDataGas(rules params.Rules, data []byte) (uint64, error) {
if rules.IsAmsterdam { if rules.IsAmsterdam {
// EIP-7976 changes how calldata is priced. // EIP-7976 changes how calldata is priced.
// From 10/40 to 64/64 for zero/non-zero bytes. // From 10/40 to 64/64 for zero/non-zero bytes.
tokens = uint64(len(data)) * params.TxTokenPerNonZeroByte
tokenCost = params.TxCostFloorPerToken7976 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 { } else {
var ( var (
z = uint64(bytes.Count(data, []byte{0})) z = uint64(bytes.Count(data, []byte{0}))
nz = uint64(len(data)) - z nz = uint64(len(data)) - z
) )
// Pre-Amsterdam // 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 tokenCost = params.TxCostFloorPerToken
} }
@ -160,14 +210,14 @@ type Message struct {
To *common.Address To *common.Address
From common.Address From common.Address
Nonce uint64 Nonce uint64
Value *big.Int Value *uint256.Int
GasLimit uint64 GasLimit uint64
GasPrice *big.Int GasPrice *uint256.Int
GasFeeCap *big.Int GasFeeCap *uint256.Int
GasTipCap *big.Int GasTipCap *uint256.Int
Data []byte Data []byte
AccessList types.AccessList AccessList types.AccessList
BlobGasFeeCap *big.Int BlobGasFeeCap *uint256.Int
BlobHashes []common.Hash BlobHashes []common.Hash
SetCodeAuthorizations []types.SetCodeAuthorization SetCodeAuthorizations []types.SetCodeAuthorization
@ -188,32 +238,64 @@ type Message struct {
// TransactionToMessage converts a transaction into a Message. // TransactionToMessage converts a transaction into a Message.
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) { 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{ msg := &Message{
From: from,
Nonce: tx.Nonce(), Nonce: tx.Nonce(),
GasLimit: tx.Gas(), GasLimit: tx.Gas(),
GasPrice: tx.GasPrice(), GasPrice: gasPrice,
GasFeeCap: tx.GasFeeCap(), GasFeeCap: gasFeeCap,
GasTipCap: tx.GasTipCap(), GasTipCap: gasTipCap,
To: tx.To(), To: tx.To(),
Value: tx.Value(), Value: value,
Data: tx.Data(), Data: tx.Data(),
AccessList: tx.AccessList(), AccessList: tx.AccessList(),
SetCodeAuthorizations: tx.SetCodeAuthorizations(), SetCodeAuthorizations: tx.SetCodeAuthorizations(),
SkipNonceChecks: false, SkipNonceChecks: false,
SkipTransactionChecks: false, SkipTransactionChecks: false,
BlobHashes: tx.BlobHashes(), BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(), BlobGasFeeCap: blobGasFeeCap,
} }
// If baseFee provided, set gasPrice to effectiveGasPrice. // If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil { if baseFee != nil {
msg.GasPrice = msg.GasPrice.Add(msg.GasTipCap, baseFee) effectiveGasPrice := new(big.Int).Add(baseFee, txGasTipCap)
if msg.GasPrice.Cmp(msg.GasFeeCap) > 0 { if effectiveGasPrice.Cmp(txGasFeeCap) > 0 {
msg.GasPrice = msg.GasFeeCap effectiveGasPrice = txGasFeeCap
} }
// EffectiveGasPrice is already capped by txGasFeeCap, therefore
// the overflow check is not required.
msg.GasPrice = uint256.MustFromBig(effectiveGasPrice)
} }
var err error return msg, nil
msg.From, err = types.Sender(s, tx)
return msg, err
} }
// ApplyMessage computes the new state by applying the given message // 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 { func (st *stateTransition) buyGas() error {
mgval := new(big.Int).SetUint64(st.msg.GasLimit) mgval := new(uint256.Int).SetUint64(st.msg.GasLimit)
mgval.Mul(mgval, st.msg.GasPrice) _, overflow := mgval.MulOverflow(mgval, st.msg.GasPrice)
balanceCheck := new(big.Int).Set(mgval) 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 { if st.msg.GasFeeCap != nil {
balanceCheck.SetUint64(st.msg.GasLimit) 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 st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
if blobGas := st.blobGasUsed(); blobGas > 0 { if blobGas := st.blobGasUsed(); blobGas > 0 {
// Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap // Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap
blobBalanceCheck := new(big.Int).SetUint64(blobGas) blobBalanceCheck := new(uint256.Int).SetUint64(blobGas)
blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap) if _, overflow := blobBalanceCheck.MulOverflow(blobBalanceCheck, st.msg.BlobGasFeeCap); overflow {
balanceCheck.Add(balanceCheck, blobBalanceCheck) 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 // Pay for blobGasUsed * actual blob fee
blobFee := new(big.Int).SetUint64(blobGas) blobBaseFee, overflow := uint256.FromBig(st.evm.Context.BlobBaseFee)
blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee) if overflow {
mgval.Add(mgval, blobFee) 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 have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
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 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) 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 { 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.gasRemaining = vm.NewGasBudget(st.msg.GasLimit)
st.initialBudget = st.gasRemaining.Copy() st.initialBudget = st.gasRemaining.Copy()
mgvalU256, _ := uint256.FromBig(mgval) st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
return nil 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) // 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 skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
if !skipCheck { 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 { if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 {
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap, return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap) msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap)
} }
// This will panic if baseFee is nil, but basefee presence is verified // This will panic if baseFee is nil, but basefee presence is verified
// as part of header validation. // 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, return fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow,
msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee) msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee)
} }
@ -410,7 +506,7 @@ func (st *stateTransition) preCheck() error {
if !skipCheck { if !skipCheck {
// This will panic if blobBaseFee is nil, but blobBaseFee presence // This will panic if blobBaseFee is nil, but blobBaseFee presence
// is verified as part of header validation. // 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, return fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow,
msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee) msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee)
} }
@ -462,7 +558,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
floorDataGas uint64 floorDataGas uint64
) )
// Check clauses 4-5, subtract intrinsic gas if everything is correct // 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 { if err != nil {
return nil, err return nil, err
} }
@ -475,7 +571,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
} }
// Gas limit suffices for the floor data cost (EIP-7623) // Gas limit suffices for the floor data cost (EIP-7623)
if rules.IsPrague { if rules.IsPrague {
floorDataGas, err = FloorDataGas(rules, msg.Data) floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -493,9 +589,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
} }
// Check clause 6 // Check clause 6
value, overflow := uint256.FromBig(msg.Value) value := msg.Value
if overflow { if value == nil {
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex()) value = new(uint256.Int)
} }
if !value.IsZero() && !st.evm.Context.CanTransfer(st.state, msg.From, value) { if !value.IsZero() && !st.evm.Context.CanTransfer(st.state, msg.From, value) {
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex()) return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
@ -579,9 +675,12 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
} }
effectiveTip := msg.GasPrice effectiveTip := msg.GasPrice
if rules.IsLondon { 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 { if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
// Skip fee payment when NoBaseFee is set and the fee fields // 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. // the coinbase when simulating calls.
} else { } else {
fee := new(uint256.Int).SetUint64(st.gasUsed()) 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) st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
// add the coinbase to the witness iff the fee is greater than 0 // add the coinbase to the witness iff the fee is greater than 0
@ -691,7 +790,7 @@ func (st *stateTransition) calcRefund() vm.GasBudget {
// exchanged at the original rate. // exchanged at the original rate.
func (st *stateTransition) returnGas() { func (st *stateTransition) returnGas() {
remaining := uint256.NewInt(st.gasRemaining.RegularGas) 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) 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 { if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining.RegularGas > 0 {

View file

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

View file

@ -116,6 +116,8 @@ const (
announceThreshold = -1 announceThreshold = -1
) )
var errLegacyTx = errors.New("legacy transaction format")
// blobTxMeta is the minimal subset of types.BlobTx necessary to validate and // blobTxMeta is the minimal subset of types.BlobTx necessary to validate and
// schedule the blob transactions into the following blocks. Only ever add the // 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 // 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 evictionBlobFeeJumps float64 // Worse blob fee (converted to fee jumps) across all previous nonces
} }
// newBlobTxMeta retrieves the indexed metadata fields from a blob transaction // blobTxForPool is the storage representation of a blob transaction in the
// and assembles a helper struct to track in memory. // blobpool.
// Requires the transaction to have a sidecar (or that we introduce a special version tag for no-sidecar). type blobTxForPool struct {
func newBlobTxMeta(id uint64, size uint64, storageSize uint32, tx *types.Transaction) *blobTxMeta { Tx *types.Transaction // tx without sidecar
if tx.BlobTxSidecar() == nil { Version byte
// This should never happen, as the pool only admits blob transactions with a sidecar 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") 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{ meta := &blobTxMeta{
hash: tx.Hash(), hash: ptx.Tx.Hash(),
vhashes: tx.BlobHashes(), vhashes: ptx.Tx.BlobHashes(),
version: tx.BlobTxSidecar().Version, version: ptx.Version,
id: id, id: id,
storageSize: storageSize, storageSize: storageSize,
size: size, size: size,
nonce: tx.Nonce(), nonce: ptx.Tx.Nonce(),
costCap: uint256.MustFromBig(tx.Cost()), costCap: uint256.MustFromBig(ptx.Tx.Cost()),
execTipCap: uint256.MustFromBig(tx.GasTipCap()), execTipCap: uint256.MustFromBig(ptx.Tx.GasTipCap()),
execFeeCap: uint256.MustFromBig(tx.GasFeeCap()), execFeeCap: uint256.MustFromBig(ptx.Tx.GasFeeCap()),
blobFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap()), blobFeeCap: uint256.MustFromBig(ptx.Tx.BlobGasFeeCap()),
execGas: tx.Gas(), execGas: ptx.Tx.Gas(),
blobGas: tx.BlobGas(), blobGas: ptx.Tx.BlobGas(),
} }
meta.basefeeJumps = dynamicFeeJumps(meta.execFeeCap) meta.basefeeJumps = dynamicFeeJumps(meta.execFeeCap)
meta.blobfeeJumps = dynamicBlobFeeJumps(meta.blobFeeCap) meta.blobfeeJumps = dynamicBlobFeeJumps(meta.blobFeeCap)
@ -460,10 +571,17 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
return err return err
} }
// Index all transactions on disk and delete anything unprocessable // 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) { index := func(id uint64, size uint32, blob []byte) {
if p.parseTransaction(id, size, blob) != nil { err := p.parseTransaction(id, size, blob)
fails = append(fails, id) 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) 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 p.store = store
if len(fails) > 0 { // Migrate legacy transactions (types.Transaction) to pooledBlobTx format.
log.Warn("Dropping invalidated blob transactions", "ids", fails) if len(convertTxs) > 0 {
dropInvalidMeter.Mark(int64(len(fails))) 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 { if err := p.store.Delete(id); err != nil {
p.Close() p.Close()
return err return err
} }
} }
} }
// Sort the indexed transactions by nonce and delete anything gapped, create // Sort the indexed transactions by nonce and delete anything gapped, create
// the eviction heap of anyone still standing // the eviction heap of anyone still standing
for addr := range p.index { 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 // parseTransaction is a callback method on pool creation that gets called for
// each transaction on disk to create the in-memory metadata index. // each transaction on disk to create the in-memory metadata index.
// Announced state is not initialized here, it needs to be iniitalized seprately. // 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 { func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
tx := new(types.Transaction) var ptx blobTxForPool
if err := rlp.DecodeBytes(blob, tx); err != nil { if err := rlp.DecodeBytes(blob, &ptx); err != nil {
// This path is impossible unless the disk data representation changes kind, content, _, splitErr := rlp.Split(blob)
// across restarts. For that ever improbable case, recover gracefully // check whether it is legacy tx type
// by ignoring this data entry. if splitErr == nil && kind == rlp.String && len(content) > 1 && content[0] == 3 {
log.Error("Failed to decode blob pool entry", "id", id, "err", err) return errLegacyTx
}
return err return err
} }
if tx.BlobTxSidecar() == nil { meta := newBlobTxMeta(id, ptx.TxSize(), size, &ptx)
log.Error("Missing sidecar in blob pool entry", "id", id, "hash", tx.Hash()) sender, err := types.Sender(p.signer, ptx.Tx)
return errors.New("missing blob sidecar") 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) { if p.lookup.exists(meta.hash) {
// This path is only possible after a crash, where deleted items are not // This path is only possible after a crash, where deleted items are not
// removed via the normal shutdown-startup procedure and thus may get // removed via the normal shutdown-startup procedure and thus may get
// partially resurrected. // partially resurrected.
log.Error("Rejecting duplicate blob pool entry", "id", id, "hash", tx.Hash()) log.Error("Rejecting duplicate blob pool entry", "id", meta.id, "hash", meta.hash)
return errors.New("duplicate blob entry") return fmt.Errorf("duplicate blob entry %d, %s", meta.id, meta.hash)
}
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
} }
if _, ok := p.index[sender]; !ok { if _, ok := p.index[sender]; !ok {
if err := p.reserver.Hold(sender); err != nil { 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) log.Error("Blobs missing for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
return return
} }
var tx types.Transaction var ptx blobTxForPool
if err = rlp.DecodeBytes(data, &tx); err != nil { if err := rlp.DecodeBytes(data, &ptx); err != nil {
log.Error("Blobs corrupted for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err) log.Error("Blobs corrupted for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
return return
} }
block, ok := inclusions[tx.Hash()] block, ok := inclusions[ptx.Tx.Hash()]
if !ok { if !ok {
log.Warn("Blob transaction swapped out by signer", "from", addr, "nonce", nonce, "id", id) log.Warn("Blob transaction swapped out by signer", "from", addr, "nonce", nonce, "id", id)
return 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) log.Warn("Failed to offload blob tx into limbo", "err", err)
return 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 { func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
// Retrieve the associated blob from the limbo. Without the blobs, we cannot // Retrieve the associated blob from the limbo. Without the blobs, we cannot
// add the transaction back into the pool as it is not mineable. // 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 { if err != nil {
log.Error("Blobs unavailable, dropping reorged tx", "err", err) log.Error("Blobs unavailable, dropping reorged tx", "err", err)
return 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, // could theoretically halt a Geth node for ~1.2s by reorging per block. However,
// this attack is financially inefficient to execute. // this attack is financially inefficient to execute.
head := p.head.Load() head := p.head.Load()
if p.chain.Config().IsOsaka(head.Number, head.Time) && tx.BlobTxSidecar().Version == types.BlobSidecarVersion0 { if p.chain.Config().IsOsaka(head.Number, head.Time) && ptx.Version == types.BlobSidecarVersion0 {
if err := tx.BlobTxSidecar().ToV1(); err != nil { sc := ptx.Sidecar()
if err := sc.ToV1(); err != nil {
log.Error("Failed to convert the legacy sidecar", "err", err) log.Error("Failed to convert the legacy sidecar", "err", err)
return 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(ptx)
blob, err := rlp.EncodeToBytes(tx)
if err != nil { 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 return err
} }
id, err := p.store.Put(blob) id, err := p.store.Put(blob)
if err != nil { 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 return err
} }
meta := newBlobTxMeta(id, ptx.TxSize(), p.store.Size(id), ptx)
// Update the indices and metrics
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), tx)
if _, ok := p.index[addr]; !ok { if _, ok := p.index[addr]; !ok {
if err := p.reserver.Hold(addr); err != nil { 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 return err
} }
p.index[addr] = []*blobTxMeta{meta} p.index[addr] = []*blobTxMeta{meta}
@ -1404,20 +1559,29 @@ func (p *BlobPool) Get(hash common.Hash) *types.Transaction {
if len(data) == 0 { if len(data) == 0 {
return nil return nil
} }
item := new(types.Transaction) var ptx blobTxForPool
if err := rlp.DecodeBytes(data, item); err != nil { if err := rlp.DecodeBytes(data, &ptx); err != nil {
id, _ := p.lookup.storeidOfTx(hash) id, _ := p.lookup.storeidOfTx(hash)
log.Error("Blobs corrupted for traced transaction", log.Error("Blobs corrupted for traced transaction",
"hash", hash, "id", id, "err", err) "hash", hash, "id", id, "err", err)
return nil 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 { 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 // 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 // Decode the blob transaction
tx := new(types.Transaction) var ptx blobTxForPool
if err := rlp.DecodeBytes(data, tx); err != nil { if err := rlp.DecodeBytes(data, &ptx); err != nil {
log.Error("Blobs corrupted for traced transaction", "id", txID, "err", err) log.Error("Blobs corrupted for traced transaction", "id", txID, "err", err)
continue continue
} }
sidecar := tx.BlobTxSidecar() sidecar := ptx.Sidecar()
if sidecar == nil {
log.Error("Blob tx without sidecar", "hash", tx.Hash(), "id", txID)
continue
}
// Traverse the blobs in the transaction // Traverse the blobs in the transaction
for i, hash := range tx.BlobHashes() { for i, hash := range ptx.Tx.BlobHashes() {
list, ok := indices[hash] list, ok := indices[hash]
if !ok { if !ok {
continue // non-interesting blob continue // non-interesting blob
@ -1517,7 +1677,8 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo
case types.BlobSidecarVersion1: case types.BlobSidecarVersion1:
cellProofs, err := sidecar.CellProofsAt(i) cellProofs, err := sidecar.CellProofsAt(i)
if err != nil { if err != nil {
return nil, nil, nil, err log.Error("Failed to get cell proofs", "id", txID, "err", err)
continue
} }
pf = cellProofs 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 // Store the tx in memory, and revalidate later
from, _ := types.Sender(p.signer, tx) from, _ := types.Sender(p.signer, tx)
allowance := p.gappedAllowance(from) 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.gapped[from] = append(p.gapped[from], tx)
p.gappedSource[tx.Hash()] = from 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])) 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 return nil
} else { } 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. // transactions by keeping the old and dropping this one.
// Thus replacing a gapped transaction with another gapped transaction // Thus replacing a gapped transaction with another gapped transaction
// is discouraged. // 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])) 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): 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, // Transaction permitted into the pool from a nonce and cost perspective,
// insert it into the database and update the indices // 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 { 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", tx.Hash(), "err", err)
return err return err
@ -1650,7 +1814,7 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
if err != nil { if err != nil {
return err return err
} }
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), tx) meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), ptx)
var ( var (
next = p.state.GetNonce(from) 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 do not recurse here, but continue to loop instead.
// We are under lock, so we can add the transaction directly. // We are under lock, so we can add the transaction directly.
if err := p.addLocked(tx, false); err == nil { 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])) log.Trace("Gapped blob transaction added to pool", "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "qlen", len(p.gapped[from]))
} else { } else {
log.Trace("Gapped blob transaction not accepted", "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "err", err) 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 { } else {
p.gapped[from] = gtxs p.gapped[from] = gtxs
} }
gappedGauge.Update(int64(len(p.gappedSource)))
} }
return nil return nil
} }
@ -2069,8 +2235,9 @@ func (p *BlobPool) evictGapped() {
keep = append(keep, gtx) keep = append(keep, gtx)
} }
} }
if len(keep) < len(txs) { if evicted := len(txs) - len(keep); evicted > 0 {
log.Trace("Evicting old gapped blob transactions", "count", len(txs)-len(keep), "from", from) gappedEvictedMeter.Mark(int64(evicted))
log.Trace("Evicting old gapped blob transactions", "count", evicted, "from", from)
} }
if len(keep) == 0 { if len(keep) == 0 {
delete(p.gapped, from) delete(p.gapped, from)
@ -2078,6 +2245,7 @@ func (p *BlobPool) evictGapped() {
p.gapped[from] = keep p.gapped[from] = keep
} }
} }
gappedGauge.Update(int64(len(p.gappedSource)))
} }
// isAnnouncable checks whether a transaction is announcable based on its // isAnnouncable checks whether a transaction is announcable based on its

View file

@ -235,6 +235,12 @@ func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64,
return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx) 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 // makeMultiBlobTx is a utility method to construct a ramdom blob tx with
// certain number of blobs in its sidecar. // 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 { 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 for _, nonce := range []uint64{0, 1, 3, 4, 6, 7} { // first gap at #2, another at #5
tx := makeTx(nonce, 1, 1, 1, gapper) tx := makeTx(nonce, 1, 1, 1, gapper)
blob, _ := rlp.EncodeToBytes(tx) blob := encodeForPool(tx)
id, _ := store.Put(blob) id, _ := store.Put(blob)
if nonce < 2 { 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 for _, nonce := range []uint64{1, 2, 3} { // first gap at #0, all set dangling
tx := makeTx(nonce, 1, 1, 1, dangler) tx := makeTx(nonce, 1, 1, 1, dangler)
blob, _ := rlp.EncodeToBytes(tx) blob := encodeForPool(tx)
id, _ := store.Put(blob) id, _ := store.Put(blob)
dangling[id] = struct{}{} 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 for _, nonce := range []uint64{0, 1, 2} { // account nonce at 3, all set filled
tx := makeTx(nonce, 1, 1, 1, filler) tx := makeTx(nonce, 1, 1, 1, filler)
blob, _ := rlp.EncodeToBytes(tx) blob := encodeForPool(tx)
id, _ := store.Put(blob) id, _ := store.Put(blob)
filled[id] = struct{}{} 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 for _, nonce := range []uint64{0, 1, 2, 3} { // account nonce at 2, half filled
tx := makeTx(nonce, 1, 1, 1, overlapper) tx := makeTx(nonce, 1, 1, 1, overlapper)
blob, _ := rlp.EncodeToBytes(tx) blob := encodeForPool(tx)
id, _ := store.Put(blob) id, _ := store.Put(blob)
if nonce >= 2 { if nonce >= 2 {
@ -595,7 +601,7 @@ func TestOpenDrops(t *testing.T) {
} else { } else {
tx = makeTx(uint64(i), 1, 1, 1, underpayer) tx = makeTx(uint64(i), 1, 1, 1, underpayer)
} }
blob, _ := rlp.EncodeToBytes(tx) blob := encodeForPool(tx)
id, _ := store.Put(blob) id, _ := store.Put(blob)
underpaid[id] = struct{}{} underpaid[id] = struct{}{}
@ -614,7 +620,7 @@ func TestOpenDrops(t *testing.T) {
} else { } else {
tx = makeTx(uint64(i), 1, 1, 1, outpricer) tx = makeTx(uint64(i), 1, 1, 1, outpricer)
} }
blob, _ := rlp.EncodeToBytes(tx) blob := encodeForPool(tx)
id, _ := store.Put(blob) id, _ := store.Put(blob)
if i < 2 { if i < 2 {
@ -636,7 +642,7 @@ func TestOpenDrops(t *testing.T) {
} else { } else {
tx = makeTx(nonce, 1, 1, 1, exceeder) tx = makeTx(nonce, 1, 1, 1, exceeder)
} }
blob, _ := rlp.EncodeToBytes(tx) blob := encodeForPool(tx)
id, _ := store.Put(blob) id, _ := store.Put(blob)
exceeded[id] = struct{}{} exceeded[id] = struct{}{}
@ -654,7 +660,7 @@ func TestOpenDrops(t *testing.T) {
} else { } else {
tx = makeTx(nonce, 1, 1, 1, overdrafter) tx = makeTx(nonce, 1, 1, 1, overdrafter)
} }
blob, _ := rlp.EncodeToBytes(tx) blob := encodeForPool(tx)
id, _ := store.Put(blob) id, _ := store.Put(blob)
if nonce < 1 { if nonce < 1 {
@ -670,7 +676,7 @@ func TestOpenDrops(t *testing.T) {
overcapped = make(map[uint64]struct{}) overcapped = make(map[uint64]struct{})
) )
for nonce := uint64(0); nonce < maxTxsPerAccount+3; nonce++ { 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) id, _ := store.Put(blob)
if nonce < maxTxsPerAccount { if nonce < maxTxsPerAccount {
@ -686,7 +692,7 @@ func TestOpenDrops(t *testing.T) {
duplicated = make(map[uint64]struct{}) duplicated = make(map[uint64]struct{})
) )
for _, nonce := range []uint64{0, 1, 2} { 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++ { for i := 0; i < int(nonce)+1; i++ {
id, _ := store.Put(blob) id, _ := store.Put(blob)
@ -705,7 +711,7 @@ func TestOpenDrops(t *testing.T) {
) )
for _, nonce := range []uint64{0, 1, 2} { for _, nonce := range []uint64{0, 1, 2} {
for i := 0; i < int(nonce)+1; i++ { 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) id, _ := store.Put(blob)
if i == 0 { 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 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) tx := makeTx(uint64(i), txExecTipCaps[i], txExecFeeCaps[i], txBlobFeeCaps[i], key)
blob, _ := rlp.EncodeToBytes(tx) blob := encodeForPool(tx)
store.Put(blob) store.Put(blob)
} }
store.Close() store.Close()
@ -934,9 +940,9 @@ func TestOpenHeap(t *testing.T) {
tx2 = makeTx(0, 1, 800, 70, key2) tx2 = makeTx(0, 1, 800, 70, key2)
tx3 = makeTx(0, 1, 1500, 110, key3) tx3 = makeTx(0, 1, 1500, 110, key3)
blob1, _ = rlp.EncodeToBytes(tx1) blob1 = encodeForPool(tx1)
blob2, _ = rlp.EncodeToBytes(tx2) blob2 = encodeForPool(tx2)
blob3, _ = rlp.EncodeToBytes(tx3) blob3 = encodeForPool(tx3)
heapOrder = []common.Address{addr2, addr1, addr3} heapOrder = []common.Address{addr2, addr1, addr3}
heapIndex = map[common.Address]int{addr2: 0, addr1: 1, addr3: 2} 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) tx2 = makeTx(0, 1, 800, 70, key2)
tx3 = makeTx(0, 1, 1500, 110, key3) tx3 = makeTx(0, 1, 1500, 110, key3)
blob1, _ = rlp.EncodeToBytes(tx1) blob1 = encodeForPool(tx1)
blob2, _ = rlp.EncodeToBytes(tx2) blob2 = encodeForPool(tx2)
blob3, _ = rlp.EncodeToBytes(tx3) blob3 = encodeForPool(tx3)
keep = []common.Address{addr1, addr3} keep = []common.Address{addr1, addr3}
drop = []common.Address{addr2} drop = []common.Address{addr2}
@ -1098,8 +1104,8 @@ func TestChangingSlotterSize(t *testing.T) {
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 0, key2, types.BlobSidecarVersion0) tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 0, key2, types.BlobSidecarVersion0)
tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3, types.BlobSidecarVersion0) tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3, types.BlobSidecarVersion0)
blob1, _ = rlp.EncodeToBytes(tx1) blob1 = encodeForPool(tx1)
blob2, _ = rlp.EncodeToBytes(tx2) blob2 = encodeForPool(tx2)
) )
// Write the two safely sized txs to store. note: although the store is // 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) tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 0, key2, types.BlobSidecarVersion0)
tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3, types.BlobSidecarVersion0) tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3, types.BlobSidecarVersion0)
blob1, _ = rlp.EncodeToBytes(tx1) blob1 = encodeForPool(tx1)
blob2, _ = rlp.EncodeToBytes(tx2) blob2 = encodeForPool(tx2)
) )
// Write the two safely sized txs to store. note: although the store is // 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. // TestBlobCountLimit tests the blobpool enforced limits on the max blob count.
func TestBlobCountLimit(t *testing.T) { func TestBlobCountLimit(t *testing.T) {
var ( var (
@ -1746,7 +1831,7 @@ func TestAdd(t *testing.T) {
// Sign the seed transactions and store them in the data store // Sign the seed transactions and store them in the data store
for _, tx := range seed.txs { for _, tx := range seed.txs {
signed := types.MustSignNewTx(keys[acc], types.LatestSigner(params.MainnetChainConfig), tx) signed := types.MustSignNewTx(keys[acc], types.LatestSigner(params.MainnetChainConfig), tx)
blob, _ := rlp.EncodeToBytes(signed) blob := encodeForPool(signed)
store.Put(blob) 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) 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) tx3 = makeMultiBlobTx(0, 1, 800, 110, 6, 12, key3, types.BlobSidecarVersion0) // [12, 18)
blob1, _ = rlp.EncodeToBytes(tx1) blob1 = encodeForPool(tx1)
blob2, _ = rlp.EncodeToBytes(tx2) blob2 = encodeForPool(tx2)
blob3, _ = rlp.EncodeToBytes(tx3) blob3 = encodeForPool(tx3)
) )
// Write the two safely sized txs to store. note: although the store is // Write the two safely sized txs to store. note: although the store is
@ -2055,6 +2140,32 @@ func TestGetBlobs(t *testing.T) {
pool.Close() 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. // fakeBilly is a billy.Database implementation which just drops data on the floor.
type fakeBilly struct { type fakeBilly struct {
billy.Database billy.Database

View file

@ -33,7 +33,7 @@ import (
type limboBlob struct { type limboBlob struct {
TxHash common.Hash // Owner transaction's hash to support resurrecting reorged txs TxHash common.Hash // Owner transaction's hash to support resurrecting reorged txs
Block uint64 // Block in which the blob transaction was included 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 // 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 // push stores a new blob transaction into the limbo, waiting until finality for
// it to be automatically evicted. // it to be automatically evicted.
func (l *limbo) push(tx *types.Transaction, block uint64) error { func (l *limbo) push(ptx *blobTxForPool, block uint64) error {
// If the blobs are already tracked by the limbo, consider it a programming hash := ptx.Tx.Hash()
// error. There's not much to do against it, but be loud. if _, ok := l.index[hash]; ok {
if _, ok := l.index[tx.Hash()]; ok { log.Error("Limbo cannot push already tracked blobs", "tx", hash)
log.Error("Limbo cannot push already tracked blobs", "tx", tx.Hash())
return errors.New("already tracked blob transaction") return errors.New("already tracked blob transaction")
} }
if err := l.setAndIndex(tx, block); err != nil { if err := l.setAndIndex(ptx, block); err != nil {
log.Error("Failed to set and index limboed blobs", "tx", tx.Hash(), "err", err) log.Error("Failed to set and index limboed blobs", "tx", hash, "err", err)
return err return err
} }
return nil 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 // 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 // it at the same time. This method should be used when a previously included blob
// transaction gets reorged out. // 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 // 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 // can happen for example if a blob transaction is mined without pushing it
// into the network first. // 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) log.Error("Failed to get and drop limboed blobs", "tx", tx, "id", id, "err", err)
return nil, 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 // 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) log.Error("Failed to get and drop limboed blobs", "tx", txhash, "id", id, "err", err)
return 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) log.Error("Failed to set and index limboed blobs", "tx", txhash, "err", err)
return 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 // setAndIndex assembles a limbo blob database entry and stores it, also updating
// the in-memory indices. // the in-memory indices.
func (l *limbo) setAndIndex(tx *types.Transaction, block uint64) error { func (l *limbo) setAndIndex(ptx *blobTxForPool, block uint64) error {
txhash := tx.Hash() txhash := ptx.Tx.Hash()
item := &limboBlob{ item := &limboBlob{
TxHash: txhash, TxHash: txhash,
Block: block, Block: block,
Tx: tx, Ptx: ptx,
} }
data, err := rlp.EncodeToBytes(item) data, err := rlp.EncodeToBytes(item)
if err != nil { if err != nil {

View file

@ -97,9 +97,15 @@ var (
addUnderpricedMeter = metrics.NewRegisteredMeter("blobpool/add/underpriced", nil) // Gas tip too low, neutral 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 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 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 addOverdraftedMeter = metrics.NewRegisteredMeter("blobpool/add/overdrafted", nil) // Balance exceeded, reject, neutral
addOvercappedMeter = metrics.NewRegisteredMeter("blobpool/add/overcapped", nil) // Per-account cap 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 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 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 addValidMeter = metrics.NewRegisteredMeter("blobpool/add/valid", nil) // Valid transaction, add, neutral
// Gapped queue metrics for observability
gappedGauge = metrics.NewRegisteredGauge("blobpool/gapped/count", nil) // Current gapped queue size
gappedPromotedMeter = metrics.NewRegisteredMeter("blobpool/gapped/promoted", nil) // Gapped txs successfully promoted to pool
gappedEvictedMeter = metrics.NewRegisteredMeter("blobpool/gapped/evicted", nil) // Gapped txs evicted due to timeout/stale
) )

View file

@ -467,8 +467,8 @@ func (pool *LegacyPool) stats() (int, int) {
// Content retrieves the data content of the transaction pool, returning all the // 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. // 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) { func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
pool.mu.Lock() pool.mu.RLock()
defer pool.mu.Unlock() defer pool.mu.RUnlock()
pending := make(map[common.Address][]*types.Transaction, len(pool.pending)) pending := make(map[common.Address][]*types.Transaction, len(pool.pending))
for addr, list := range 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 { if filter.BlobTxs {
return nil, 0 return nil, 0
} }
pool.mu.Lock() pool.mu.RLock()
defer pool.mu.Unlock() defer pool.mu.RUnlock()
var count int var count int
pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending)) pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))

View file

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

View file

@ -125,7 +125,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
} }
// Ensure the transaction has more gas than the bare minimum needed to cover // Ensure the transaction has more gas than the bare minimum needed to cover
// the transaction metadata // 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 { if err != nil {
return err return err
} }
@ -134,7 +134,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
} }
// Ensure the transaction can cover floor data gas. // Ensure the transaction can cover floor data gas.
if rules.IsPrague { if rules.IsPrague {
floorDataGas, err := core.FloorDataGas(rules, tx.Data()) floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
if err != nil { if err != nil {
return err return err
} }

View file

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

View file

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

View file

@ -82,6 +82,9 @@ const (
// beaconUpdateWarnFrequency is the frequency at which to warn the user that // beaconUpdateWarnFrequency is the frequency at which to warn the user that
// the beacon client is offline. // the beacon client is offline.
beaconUpdateWarnFrequency = 5 * time.Minute beaconUpdateWarnFrequency = 5 * time.Minute
// maxReorgDepth is the maximum reorg depth accepted via forkchoiceUpdated.
maxReorgDepth = 32
) )
type ConsensusAPI struct { 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) { 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") ctx, _, spanEnd := telemetry.StartSpan(ctx, "engine.forkchoiceUpdated")
defer spanEnd(&err) defer spanEnd(&err)
api.forkchoiceLock.Lock() api.forkchoiceLock.Lock()
defer api.forkchoiceLock.Unlock() 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 // 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. // missing and we are requested to generate the payload in slot.
} else { } else {
// If the head block is already in our canonical chain, the beacon client is if finalized := api.eth.BlockChain().CurrentFinalBlock(); finalized != nil && block.NumberU64() <= finalized.Number.Uint64() {
// probably resyncing. Ignore the update. log.Info("Skipping beacon update to finalized ancestor", "number", block.NumberU64(), "hash", update.HeadBlockHash)
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
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() api.eth.SetSynced()
@ -629,6 +646,7 @@ func (api *ConsensusAPI) getBlobs(hashes []common.Hash, v2 bool) ([]*engine.Blob
return nil, engine.InvalidParams.With(err) return nil, engine.InvalidParams.With(err)
} }
// Validate the blobs from the pool and assemble the response // Validate the blobs from the pool and assemble the response
filled := 0
res := make([]*engine.BlobAndProofV2, len(hashes)) res := make([]*engine.BlobAndProofV2, len(hashes))
for i := range blobs { for i := range blobs {
// The blob has been evicted since the last AvailableBlobs call. // 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][:], Blob: blobs[i][:],
CellProofs: cellProofs, CellProofs: cellProofs,
} }
filled++
} }
if len(res) == len(hashes) { if filled == len(hashes) {
getBlobsRequestCompleteHit.Inc(1) getBlobsRequestCompleteHit.Inc(1)
} else if len(res) > 0 { } else if filled > 0 {
getBlobsRequestPartialHit.Inc(1) getBlobsRequestPartialHit.Inc(1)
} else { } else {
getBlobsRequestMiss.Inc(1) getBlobsRequestMiss.Inc(1)

View file

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

View file

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

View file

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

View file

@ -16,10 +16,24 @@
package downloader 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 { // SyncEventType represents the type of sync event
Latest *types.Header type SyncEventType int
const (
SyncStarted SyncEventType = iota
SyncFailed
SyncCompleted
)
// SyncEvent represents a downloader synchronization event
type SyncEvent struct {
Type SyncEventType
Mode ethconfig.SyncMode
Err error // Set when Type is SyncFailed
Latest *types.Header // Set when Type is SyncCompleted
} }
type StartEvent struct{}
type FailedEvent struct{ Err error }

View file

@ -323,25 +323,32 @@ func (d *Downloader) concurrentFetch(queue typedQueue) error {
delete(pending, res.Req.Peer) delete(pending, res.Req.Peer)
delete(stales, 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 // If the peer was previously banned and failed to deliver its pack
// in a reasonable time frame, ignore its message. // in a reasonable time frame, ignore its message.
if peer := d.peers.Peer(res.Req.Peer); peer != nil { peer := d.peers.Peer(res.Req.Peer)
// Deliver the received chunk of data and check chain validity if peer == nil {
accepted, err := queue.deliver(peer, res) res.Done <- nil
if errors.Is(err, errInvalidChain) { res.Req.Close()
return err continue
} }
// Unless a peer delivered something completely else than requested (usually // Deliver the received chunk of data and check chain validity
// caused by a timed out request which came through in the end), set it to accepted, err := queue.deliver(peer, res)
// idle. If the delivery's stale, the peer should have already been idled. // Unless a peer delivered something completely else than requested (usually
if !errors.Is(err, errStaleDelivery) { // caused by a timed out request which came through in the end), set it to
queue.updateCapacity(peer, accepted, res.Time) // 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(): case cont := <-queue.waker():
@ -352,3 +359,11 @@ func (d *Downloader) concurrentFetch(queue typedQueue) error {
} }
} }
} }
// validityErrorOfRequest returns err if it is related to block validity, and nil otherwise.
func validityErrorOfRequest(err error) error {
if errors.Is(err, errInvalidBody) || errors.Is(err, errInvalidReceipt) {
return err
}
return nil
}

View file

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

View file

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/pathdb" "github.com/ethereum/go-ethereum/triedb/pathdb"
) )
@ -59,6 +60,7 @@ var Defaults = Config{
StateHistory: pathdb.Defaults.StateHistory, StateHistory: pathdb.Defaults.StateHistory,
TrienodeHistory: pathdb.Defaults.TrienodeHistory, TrienodeHistory: pathdb.Defaults.TrienodeHistory,
NodeFullValueCheckpoint: pathdb.Defaults.FullValueCheckpoint, NodeFullValueCheckpoint: pathdb.Defaults.FullValueCheckpoint,
BinTrieGroupDepth: triedb.DefaultBinTrieGroupDepth,
DatabaseCache: 2048, DatabaseCache: 2048,
TrieCleanCache: 614, TrieCleanCache: 614,
TrieDirtyCache: 1024, TrieDirtyCache: 1024,
@ -125,6 +127,11 @@ type Config struct {
// consistent with persistent state. // consistent with persistent state.
StateScheme string `toml:",omitempty"` 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 // 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 // canonical chain of all remote peers. Setting the option makes geth verify the
// presence of these blocks for every new peer connection. // presence of these blocks for every new peer connection.

View file

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

View file

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

View file

@ -107,7 +107,6 @@ type handlerConfig struct {
Network uint64 // Network identifier to advertise Network uint64 // Network identifier to advertise
Sync ethconfig.SyncMode // Whether to snap or full sync Sync ethconfig.SyncMode // Whether to snap or full sync
BloomCache uint64 // Megabytes to alloc for snap sync bloom 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 RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
} }
@ -126,7 +125,6 @@ type handler struct {
peers *peerSet peers *peerSet
txBroadcastKey [16]byte txBroadcastKey [16]byte
eventMux *event.TypeMux
txsCh chan core.NewTxsEvent txsCh chan core.NewTxsEvent
txsSub event.Subscription txsSub event.Subscription
blockRange *blockRangeState blockRange *blockRangeState
@ -144,14 +142,9 @@ type handler struct {
// newHandler returns a handler for all Ethereum chain management protocol. // newHandler returns a handler for all Ethereum chain management protocol.
func newHandler(config *handlerConfig) (*handler, error) { 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{ h := &handler{
nodeID: config.NodeID, nodeID: config.NodeID,
networkID: config.Network, networkID: config.Network,
eventMux: config.EventMux,
database: config.Database, database: config.Database,
txpool: config.TxPool, txpool: config.TxPool,
chain: config.Chain, chain: config.Chain,
@ -163,7 +156,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
handlerStartCh: make(chan struct{}), handlerStartCh: make(chan struct{}),
} }
// Construct the downloader (long sync) // 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 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) { 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 // broadcast block range
h.wg.Add(1) h.wg.Add(1)
h.blockRange = newBlockRangeState(h.chain, h.eventMux) h.blockRange = newBlockRangeState(h.chain, h.downloader)
go h.blockRangeLoop(h.blockRange) go h.blockRangeLoop(h.blockRange)
// start sync handlers // start sync handlers
@ -536,16 +529,19 @@ type blockRangeState struct {
next atomic.Pointer[eth.BlockRangeUpdatePacket] next atomic.Pointer[eth.BlockRangeUpdatePacket]
headCh chan core.ChainHeadEvent headCh chan core.ChainHeadEvent
headSub event.Subscription 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) headCh := make(chan core.ChainHeadEvent, chainHeadChanSize)
headSub := chain.SubscribeChainHeadEvent(headCh) 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{ st := &blockRangeState{
headCh: headCh, headCh: headCh,
headSub: headSub, headSub: headSub,
syncCh: syncCh,
syncSub: syncSub, syncSub: syncSub,
} }
st.update(chain, chain.CurrentBlock()) st.update(chain, chain.CurrentBlock())
@ -561,11 +557,8 @@ func (h *handler) blockRangeLoop(st *blockRangeState) {
for { for {
select { select {
case ev := <-st.syncSub.Chan(): case ev := <-st.syncCh:
if ev == nil { if ev.Type == downloader.SyncStarted && ev.Mode == ethconfig.SnapSync {
continue
}
if _, ok := ev.Data.(downloader.StartEvent); ok && h.downloader.ConfigSyncMode() == ethconfig.SnapSync {
h.blockRangeWhileSnapSyncing(st) h.blockRangeWhileSnapSyncing(st)
} }
case <-st.headCh: case <-st.headCh:
@ -593,12 +586,8 @@ func (h *handler) blockRangeWhileSnapSyncing(st *blockRangeState) {
h.broadcastBlockRange(st) h.broadcastBlockRange(st)
} }
// back to processing head block updates when sync is done // back to processing head block updates when sync is done
case ev := <-st.syncSub.Chan(): case ev := <-st.syncCh:
if ev == nil { if ev.Type == downloader.SyncFailed || ev.Type == downloader.SyncCompleted {
continue
}
switch ev.Data.(type) {
case downloader.FailedEvent, downloader.DoneEvent:
return return
} }
// ignore head updates, but exit when the subscription ends // ignore head updates, but exit when the subscription ends

View file

@ -424,16 +424,20 @@ func testGetBlockBodies(t *testing.T, protocol uint) {
{0, []common.Hash{backend.chain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable {0, []common.Hash{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 {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{ {0, []common.Hash{
{}, {},
backend.chain.GetBlockByNumber(1).Hash(), backend.chain.GetBlockByNumber(1).Hash(),
{},
backend.chain.GetBlockByNumber(10).Hash(), backend.chain.GetBlockByNumber(10).Hash(),
{}, }, []bool{false, true, true}, 0},
backend.chain.GetBlockByNumber(100).Hash(),
{},
}, []bool{false, true, false, true, false, true, false}, 3},
} }
// Run each of the tests and verify the results against the chain // Run each of the tests and verify the results against the chain
for i, tt := range tests { for i, tt := range tests {

View file

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

View file

@ -248,13 +248,10 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
evm := vm.NewEVM(context, statedb, eth.blockchain.Config(), vm.Config{}) evm := vm.NewEVM(context, statedb, eth.blockchain.Config(), vm.Config{})
defer evm.Release() defer evm.Release()
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm) // Run pre-execution system calls
} core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), eth.blockchain.Config(), evm, block.Number(), block.Time())
// 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)
}
if txIndex == 0 && len(block.Transactions()) == 0 { if txIndex == 0 && len(block.Transactions()) == 0 {
return nil, context, statedb, release, nil return nil, context, statedb, release, nil
} }

View file

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth" "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/eth/ethconfig"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
@ -37,32 +38,40 @@ type syncReq struct {
errc chan error 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 // Syncer is an auxiliary service that allows Geth to perform full sync
// alone without consensus-layer attached. Users must specify a valid block hash // alone without consensus-layer attached. Users must specify a valid block hash
// as the sync target. // 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 // This tool can be applied to different networks, no matter it's pre-merge or
// post-merge, but only for full-sync. // post-merge, but only for full-sync.
type Syncer struct { type Syncer struct {
stack *node.Node stack *node.Node
backend *eth.Ethereum backend *eth.Ethereum
target common.Hash request chan *syncReq
request chan *syncReq closed chan struct{}
closed chan struct{} wg sync.WaitGroup
wg sync.WaitGroup
exitWhenSynced bool config Config
} }
// Register registers the synchronization override service into the node // Register registers the synchronization override service into the node
// stack for launching and stopping the service controlled by 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{ s := &Syncer{
stack: stack, stack: stack,
backend: backend, backend: backend,
target: target, request: make(chan *syncReq),
request: make(chan *syncReq), closed: make(chan struct{}),
closed: make(chan struct{}), config: cfg,
exitWhenSynced: exitWhenSynced,
} }
stack.RegisterAPIs(s.APIs()) stack.RegisterAPIs(s.APIs())
stack.RegisterLifecycle(s) stack.RegisterLifecycle(s)
@ -88,9 +97,11 @@ func (s *Syncer) run() {
var ( var (
target *types.Header 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 { for {
select { select {
case req := <-s.request: case req := <-s.request:
@ -137,35 +148,50 @@ func (s *Syncer) run() {
} }
} }
case <-ticker.C: case ev := <-syncCh:
if target == nil { if ev.Type == downloader.SyncStarted {
log.Debug("Synchronization started")
continue 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 // Terminate the node if the target has been reached
if s.exitWhenSynced { if s.config.ExitWhenSynced {
if block := s.backend.BlockChain().GetBlockByHash(target.Hash()); block != nil { var synced bool
log.Info("Sync target reached", "number", block.NumberU64(), "hash", block.Hash()) var block *types.Header
go s.stack.Close() // async since we need to close ourselves if target != nil {
return 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. if synced {
// The finalized marker is set two epochs behind the target, log.Info("Sync target reached", "number", block.Number.Uint64(), "hash", block.Hash())
// and the safe marker is set one epoch behind the target. go s.stack.Close() // async since we need to close ourselves
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)
} }
} }
@ -179,10 +205,10 @@ func (s *Syncer) run() {
func (s *Syncer) Start() error { func (s *Syncer) Start() error {
s.wg.Add(1) s.wg.Add(1)
go s.run() go s.run()
if s.target == (common.Hash{}) { if s.config.TargetBlock == (common.Hash{}) {
return nil return nil
} }
return s.Sync(s.target) return s.Sync(s.config.TargetBlock)
} }
// Stop terminates the synchronization service and stop all background activities. // Stop terminates the synchronization service and stop all background activities.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -233,7 +233,10 @@ func (t *flatCallTracer) GetResult() (json.RawMessage, error) {
if err != nil { if err != nil {
return nil, err 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. // Stop terminates execution of the tracer at the first opportune moment.

View file

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

View file

@ -63,22 +63,30 @@ func newMuxTracerFromConfig(ctx *tracers.Context, cfg json.RawMessage, chainConf
// //
// The names parameter associates a label with each tracer, used as keys in // The names parameter associates a label with each tracer, used as keys in
// the aggregated JSON result returned by GetResult. // 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) { func NewMuxTracer(names []string, objects []*tracers.Tracer) (*tracers.Tracer, error) {
t := &muxTracer{names: names, tracers: objects} t := &muxTracer{names: names, tracers: objects}
return &tracers.Tracer{ return &tracers.Tracer{
Hooks: &tracing.Hooks{ Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart, OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd, OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter, OnEnter: t.OnEnter,
OnExit: t.OnExit, OnExit: t.OnExit,
OnOpcode: t.OnOpcode, OnOpcode: t.OnOpcode,
OnFault: t.OnFault, OnFault: t.OnFault,
OnGasChange: t.OnGasChange, OnGasChange: t.OnGasChange,
OnBalanceChange: t.OnBalanceChange, OnBalanceChange: t.OnBalanceChange,
OnNonceChange: t.OnNonceChange, OnNonceChangeV2: t.OnNonceChangeV2,
OnCodeChange: t.OnCodeChange, OnCodeChangeV2: t.OnCodeChangeV2,
OnStorageChange: t.OnStorageChange, OnStorageChange: t.OnStorageChange,
OnLog: t.OnLog, OnLog: t.OnLog,
OnSystemCallStartV2: t.OnSystemCallStart,
OnSystemCallEnd: t.OnSystemCallEnd,
}, },
GetResult: t.GetResult, GetResult: t.GetResult,
Stop: t.Stop, 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 { 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) 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) { 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 { for _, t := range t.tracers {
if t.OnCodeChangeV2 != nil { if t.OnCodeChangeV2 != nil {
t.OnCodeChangeV2(a, prevCodeHash, prev, codeHash, code, reason) 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. // GetResult returns an empty json object.
func (t *muxTracer) GetResult() (json.RawMessage, error) { func (t *muxTracer) GetResult() (json.RawMessage, error) {
resObject := make(map[string]json.RawMessage) resObject := make(map[string]json.RawMessage)

View file

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

View file

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

View file

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

View file

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

View file

@ -861,6 +861,12 @@ func TestSimulateV1(t *testing.T) {
if results[0].Calls[0].Error != nil { if results[0].Calls[0].Error != nil {
t.Errorf("expected no error, got %v", results[0].Calls[0].Error) 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) { func TestSimulateV1WithBlockOverrides(t *testing.T) {

View file

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

View file

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

View file

@ -41,12 +41,19 @@ type GoToolchain struct {
func (g *GoToolchain) Go(command string, args ...string) *exec.Cmd { func (g *GoToolchain) Go(command string, args ...string) *exec.Cmd {
tool := g.goTool(command, args...) tool := g.goTool(command, args...)
// Configure environment for cross build. // Configure environment for cross build. Force CGO_ENABLED=1 whenever
if g.GOARCH != "" && g.GOARCH != runtime.GOARCH { // 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") tool.Env = append(tool.Env, "CGO_ENABLED=1")
}
if crossArch {
tool.Env = append(tool.Env, "GOARCH="+g.GOARCH) 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) tool.Env = append(tool.Env, "GOOS="+g.GOOS)
} }
// Configure C compiler. // Configure C compiler.

View file

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

View file

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

View file

@ -734,6 +734,10 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
if err := blockOverrides.Apply(&blockCtx); err != nil { if err := blockOverrides.Apply(&blockCtx); err != nil {
return nil, err 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) rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time)
precompiles := vm.ActivePrecompiledContracts(rules) precompiles := vm.ActivePrecompiledContracts(rules)
@ -992,6 +996,9 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} {
if head.RequestsHash != nil { if head.RequestsHash != nil {
result["requestsHash"] = head.RequestsHash result["requestsHash"] = head.RequestsHash
} }
if head.BlockAccessListHash != nil {
result["balHash"] = head.BlockAccessListHash
}
if head.SlotNumber != nil { if head.SlotNumber != nil {
result["slotNumber"] = hexutil.Uint64(*head.SlotNumber) result["slotNumber"] = hexutil.Uint64(*head.SlotNumber)
} }

View file

@ -1315,6 +1315,27 @@ func TestCall(t *testing.T) {
}, },
expectErr: errors.New(`block override "withdrawals" is not supported for this RPC method`), 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 { for _, tc := range testSuite {
result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides) 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") 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) { func TestSignTransaction(t *testing.T) {
t.Parallel() t.Parallel()
// Initialize test accounts // Initialize test accounts

View file

@ -318,12 +318,9 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
if precompiles != nil { if precompiles != nil {
evm.SetPrecompiles(precompiles) evm.SetPrecompiles(precompiles)
} }
if sim.chainConfig.IsPrague(header.Number, header.Time) || sim.chainConfig.IsUBT(header.Number, header.Time) { // Run pre-execution system calls
core.ProcessParentBlockHash(header.ParentHash, evm) core.PreExecution(ctx, header.ParentBeaconRoot, header.ParentHash, sim.chainConfig, evm, header.Number, header.Time)
}
if header.ParentBeaconRoot != nil {
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, evm)
}
var allLogs []*types.Log var allLogs []*types.Log
for i, call := range block.Calls { for i, call := range block.Calls {
// Terminate if the context is cancelled // Terminate if the context is cancelled
@ -393,22 +390,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
header.BlobGasUsed = &blobGasUsed header.BlobGasUsed = &blobGasUsed
} }
// Process EIP-7685 requests // Run post-execution system calls
var requests [][]byte requests, err := core.PostExecution(ctx, sim.chainConfig, header.Number, header.Time, allLogs, evm)
if sim.chainConfig.IsPrague(header.Number, header.Time) { if err != nil {
requests = [][]byte{} return nil, nil, nil, err
// 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
}
} }
if requests != nil { if requests != nil {
reqHash := types.CalcRequestsHash(requests) reqHash := types.CalcRequestsHash(requests)
@ -417,7 +402,12 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
blockBody := &types.Body{ blockBody := &types.Body{
Transactions: txes, 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} chainHeadReader := &simChainHeadReader{ctx, sim.b}

View file

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

View file

@ -208,21 +208,9 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams,
} }
// Collect consensus-layer requests if Prague is enabled. // Collect consensus-layer requests if Prague is enabled.
var requests [][]byte requests, err := core.PostExecution(ctx, miner.chainConfig, work.header.Number, work.header.Time, allLogs, work.evm)
if miner.chainConfig.IsPrague(work.header.Number, work.header.Time) { if err != nil {
requests = [][]byte{} return &newPayloadResult{err: err}
// 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}
}
} }
if requests != nil { if requests != nil {
reqHash := types.CalcRequestsHash(requests) 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) log.Error("Failed to create sealing context", "err", err)
return nil, err return nil, err
} }
if header.ParentBeaconRoot != nil { // Run pre-execution system calls
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, env.evm) core.PreExecution(ctx, header.ParentBeaconRoot, header.ParentHash, miner.chainConfig, env.evm, header.Number, header.Time)
}
if miner.chainConfig.IsPrague(header.Number, header.Time) {
core.ProcessParentBlockHash(header.ParentHash, env.evm)
}
return env, nil return env, nil
} }

View file

@ -35,7 +35,6 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
@ -44,7 +43,6 @@ import (
// Node is a container on which services can be registered. // Node is a container on which services can be registered.
type Node struct { type Node struct {
eventmux *event.TypeMux
config *Config config *Config
accman *accounts.Manager accman *accounts.Manager
log log.Logger log log.Logger
@ -108,7 +106,6 @@ func New(conf *Config) (*Node, error) {
node := &Node{ node := &Node{
config: conf, config: conf,
inprocHandler: server, inprocHandler: server,
eventmux: new(event.TypeMux),
log: conf.Logger, log: conf.Logger,
stop: make(chan struct{}), stop: make(chan struct{}),
server: &p2p.Server{Config: conf.P2P}, server: &p2p.Server{Config: conf.P2P},
@ -692,12 +689,6 @@ func (n *Node) WSAuthEndpoint() string {
return "ws://" + n.wsAuth.listenAddr() + n.wsAuth.wsConfig.prefix 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 // 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 // previous can be found) from within the node's instance directory. If the node has no
// data directory, an in-memory database is returned. // data directory, an in-memory database is returned.

View file

@ -67,7 +67,10 @@ type tcpDialer struct {
} }
func (t tcpDialer) Dial(ctx context.Context, dest *enode.Node) (net.Conn, error) { 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()) return t.d.DialContext(ctx, "tcp", addr.String())
} }

View file

@ -753,6 +753,41 @@ func (tab *Table) deleteNode(n *enode.Node) {
// waitForNodes blocks until the table contains at least n nodes. // waitForNodes blocks until the table contains at least n nodes.
func (tab *Table) waitForNodes(ctx context.Context, n int) error { 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) { getlength := func() (count int) {
for _, b := range &tab.buckets { for _, b := range &tab.buckets {
count += len(b.entries) count += len(b.entries)
@ -760,28 +795,24 @@ func (tab *Table) waitForNodes(ctx context.Context, n int) error {
return count return count
} }
var ch chan *enode.Node
for { for {
tab.mutex.Lock() tab.mutex.Lock()
if getlength() >= n { if getlength() >= n {
tab.mutex.Unlock() tab.mutex.Unlock()
return nil return nil
} }
if ch == nil { if notify == nil {
// Init subscription. // Lazily init the subscription. Do this while holding the
ch = make(chan *enode.Node) // lock so we don't miss any events that change the node count.
sub := tab.nodeFeed.Subscribe(ch) sub := initsub()
defer sub.Unsubscribe() defer sub.Unsubscribe()
} }
tab.mutex.Unlock() tab.mutex.Unlock()
// Wait for a node add event. // Wait for table event.
select { if _, ok := <-notify; !ok {
case <-ch: break
case <-ctx.Done():
return ctx.Err()
case <-tab.closeReq:
return errClosed
} }
} }
return notifyErr
} }

View file

@ -17,6 +17,7 @@
package discover package discover
import ( import (
"context"
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
"math/rand" "math/rand"
@ -550,6 +551,45 @@ func TestSetFallbackNodes_DNSHostname(t *testing.T) {
t.Logf("resolved localhost to %v", resolved.IPAddr()) 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 { func newkey() *ecdsa.PrivateKey {
key, err := crypto.GenerateKey() key, err := crypto.GenerateKey()
if err != nil { if err != nil {

View file

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

View file

@ -17,6 +17,7 @@
package core package core
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
@ -309,7 +310,8 @@ func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hex
if sig[64] != 27 && sig[64] != 28 { if sig[64] != 27 && sig[64] != 28 {
return common.Address{}, errors.New("invalid Ethereum signature (V is not 27 or 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) hash := accounts.TextHash(data)
rpk, err := crypto.SigToPub(hash, sig) rpk, err := crypto.SigToPub(hash, sig)
if err != nil { if err != nil {

View file

@ -35,7 +35,6 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/holiman/uint256"
) )
func initMatcher(st *testMatcher) { func initMatcher(st *testMatcher) {
@ -329,7 +328,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
initialGas := vm.NewGasBudget(msg.GasLimit) initialGas := vm.NewGasBudget(msg.GasLimit)
// Execute the message. // 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 { if err != nil {
b.Error(err) b.Error(err)
return return

View file

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

View file

@ -81,7 +81,7 @@ func (tt *TransactionTest) Run() error {
return return
} }
// Intrinsic gas // 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 { if err != nil {
return return
} }
@ -92,7 +92,7 @@ func (tt *TransactionTest) Run() error {
if rules.IsPrague { if rules.IsPrague {
var floorDataGas uint64 var floorDataGas uint64
floorDataGas, err = core.FloorDataGas(rules, tx.Data()) floorDataGas, err = core.FloorDataGas(rules, tx.Data(), tx.AccessList())
if err != nil { if err != nil {
return return
} }

View file

@ -27,8 +27,19 @@ const (
NodeTypeBytes = 1 // Size of node type prefix in serialization NodeTypeBytes = 1 // Size of node type prefix in serialization
HashSize = 32 // Size of a hash in bytes HashSize = 32 // Size of a hash in bytes
StemBitmapSize = 32 // Size of the bitmap in a stem node (256 values = 32 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 ( const (
nodeTypeStem = iota + 1 nodeTypeStem = iota + 1
nodeTypeInternal nodeTypeInternal

View file

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

View file

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

View file

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

View file

@ -205,7 +205,7 @@ func (it *binaryNodeIterator) Path() []byte {
} }
func (it *binaryNodeIterator) NodeBlob() []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. // Leaf reports whether the iterator is currently positioned at a leaf value.

View file

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

View file

@ -107,18 +107,83 @@ func (s *nodeStore) hashInternal(idx uint32) common.Hash {
return node.hash return node.hash
} }
// SerializeNode serializes a node into the flat on-disk format. // serializeSubtree recursively collects child hashes from a subtree of InternalNodes.
func (s *nodeStore) serializeNode(ref nodeRef) []byte { // 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() { switch ref.Kind() {
case kindInternal: 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()) 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 serialized[0] = nodeTypeInternal
lh := s.computeHash(node.left) serialized[1] = byte(groupDepth) // group depth => bitmap size for a sparse group
rh := s.computeHash(node.right) copy(serialized[2:2+bitmapSize], bitmap)
copy(serialized[NodeTypeBytes:NodeTypeBytes+HashSize], lh[:])
copy(serialized[NodeTypeBytes+HashSize:], rh[:]) offset := NodeTypeBytes + 1 + bitmapSize
return serialized[:] for _, h := range hashes {
copy(serialized[offset:offset+HashSize], h.Bytes())
offset += HashSize
}
return serialized
case kindStem: case kindStem:
sn := s.getStem(ref.Index()) 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) 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) { func (s *nodeStore) decodeNode(serialized []byte, depth int, hn common.Hash, mustRecompute, dirty bool) (nodeRef, error) {
if len(serialized) == 0 { if len(serialized) == 0 {
return emptyRef, nil return emptyRef, nil
@ -170,31 +288,23 @@ func (s *nodeStore) decodeNode(serialized []byte, depth int, hn common.Hash, mus
switch serialized[0] { switch serialized[0] {
case nodeTypeInternal: 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 return emptyRef, errInvalidSerializedLength
} }
var leftHash, rightHash common.Hash groupDepth := int(serialized[1])
copy(leftHash[:], serialized[NodeTypeBytes:NodeTypeBytes+HashSize]) if groupDepth < 1 || groupDepth > MaxGroupDepth {
copy(rightHash[:], serialized[NodeTypeBytes+HashSize:]) 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 hashIdx := 0
if leftHash != (common.Hash{}) { return s.deserializeSubtree(hn, groupDepth, 0, depth, bitmap, hashData, &hashIdx, mustRecompute, dirty)
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
case nodeTypeStem: case nodeTypeStem:
if len(serialized) < NodeTypeBytes+StemSize+StemBitmapSize { 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. // 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 // Invariant: any ancestor of a node that needs flushing is itself marked, so a
// clean root means the whole subtree is clean. // 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() { switch ref.Kind() {
case kindEmpty:
return nil
case kindInternal: case kindInternal:
node := s.getInternal(ref.Index()) node := s.getInternal(ref.Index())
if !node.dirty { if !node.dirty {
return nil return
} }
// Reuse path buffer across children: flushfn consumers // Only flush at group boundaries (depth % groupDepth == 0)
// (NodeSet.AddNode, tracer.Get) clone via string(path), so in-place if int(node.depth)%groupDepth == 0 {
// mutation is safe. // We're at a group boundary - first collect any nodes in deeper groups,
path = append(path, 0) // then flush this group
if err := s.collectNodes(node.left, path, flushfn); err != nil { s.collectChildGroups(node, path, flushfn, groupDepth, groupDepth-1)
return err flushfn(path, s.computeHash(ref), s.serializeNode(ref, groupDepth))
node.dirty = false
return
} }
path[len(path)-1] = 1 // Not at a group boundary - this shouldn't happen if we're called correctly from root
if err := s.collectNodes(node.right, path, flushfn); err != nil { // but handle it by continuing to traverse
return err s.collectChildGroups(node, path, flushfn, groupDepth, groupDepth-(int(node.depth)%groupDepth)-1)
}
path = path[:len(path)-1]
flushfn(path, s.computeHash(ref), s.serializeNode(ref))
node.dirty = false
return nil
case kindStem: case kindStem:
sn := s.getStem(ref.Index()) sn := s.getStem(ref.Index())
if !sn.dirty { 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 sn.dirty = false
return nil case kindHashed, kindEmpty:
case kindHashed:
return nil // Already committed
default: 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 { func (s *nodeStore) toDot(ref nodeRef, parent, path string) string {
switch ref.Kind() { switch ref.Kind() {
case kindInternal: case kindInternal:

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