mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 08:49:29 +00:00
resolve conflict
This commit is contained in:
commit
3266f5ad0a
108 changed files with 2496 additions and 973 deletions
|
|
@ -145,7 +145,7 @@ jobs:
|
|||
|
||||
windows:
|
||||
name: Windows Build
|
||||
runs-on: "win-11"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
|
@ -155,57 +155,46 @@ jobs:
|
|||
go-version: 1.24
|
||||
cache: false
|
||||
|
||||
# Note: gcc.exe only works properly if the corresponding bin/ directory is
|
||||
# contained in PATH.
|
||||
- name: Install cross toolchain
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get -yq --no-install-suggests --no-install-recommends install \
|
||||
gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 nsis
|
||||
|
||||
- name: "Build (amd64)"
|
||||
shell: cmd
|
||||
run: |
|
||||
set PATH=%GETH_MINGW%\bin;%PATH%
|
||||
go run build/ci.go install -dlgo -arch amd64 -cc %GETH_MINGW%\bin\gcc.exe
|
||||
env:
|
||||
GETH_MINGW: 'C:\msys64\mingw64'
|
||||
go run build/ci.go install -dlgo -os windows -arch amd64 -cc x86_64-w64-mingw32-gcc
|
||||
|
||||
- name: "Create/upload archive (amd64)"
|
||||
shell: cmd
|
||||
run: |
|
||||
go run build/ci.go archive -arch amd64 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
go run build/ci.go archive -os windows -arch amd64 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
env:
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
- name: "Create/upload NSIS installer (amd64)"
|
||||
shell: cmd
|
||||
run: |
|
||||
set "PATH=C:\Program Files (x86)\NSIS;%PATH%"
|
||||
go run build/ci.go nsis -arch amd64 -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
del /Q build\bin\*
|
||||
rm -f build/bin/*
|
||||
env:
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
- name: "Build (386)"
|
||||
shell: cmd
|
||||
run: |
|
||||
set PATH=%GETH_MINGW%\bin;%PATH%
|
||||
go run build/ci.go install -dlgo -arch 386 -cc %GETH_MINGW%\bin\gcc.exe
|
||||
env:
|
||||
GETH_MINGW: 'C:\msys64\mingw32'
|
||||
go run build/ci.go install -dlgo -os windows -arch 386 -cc i686-w64-mingw32-gcc
|
||||
|
||||
- name: "Create/upload archive (386)"
|
||||
shell: cmd
|
||||
run: |
|
||||
go run build/ci.go archive -arch 386 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
go run build/ci.go archive -os windows -arch 386 -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
env:
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
- name: "Create/upload NSIS installer (386)"
|
||||
shell: cmd
|
||||
run: |
|
||||
set "PATH=C:\Program Files (x86)\NSIS;%PATH%"
|
||||
go run build/ci.go nsis -arch 386 -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
del /Q build\bin\*
|
||||
rm -f build/bin/*
|
||||
env:
|
||||
WINDOWS_SIGNING_KEY: ${{ secrets.WINDOWS_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
|
|
|||
|
|
@ -176,6 +176,13 @@ var (
|
|||
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
|
||||
// an empty contract behind.
|
||||
ErrNoCodeAfterDeploy = bind2.ErrNoCodeAfterDeploy
|
||||
|
||||
// ErrNoEventSignature is returned when a log entry has no topics.
|
||||
ErrNoEventSignature = bind2.ErrNoEventSignature
|
||||
|
||||
// ErrEventSignatureMismatch is returned when a log's topic[0] does not match
|
||||
// the expected event signature.
|
||||
ErrEventSignatureMismatch = bind2.ErrEventSignatureMismatch
|
||||
)
|
||||
|
||||
// ContractCaller defines the methods needed to allow operating with a contract on a read
|
||||
|
|
|
|||
|
|
@ -910,7 +910,7 @@ func TestUnpackTuple(t *testing.T) {
|
|||
},
|
||||
},
|
||||
FieldT: T{
|
||||
big.NewInt(0), big.NewInt(1),
|
||||
big.NewInt(0).SetBits([]big.Word{}), big.NewInt(1),
|
||||
},
|
||||
A: big.NewInt(1),
|
||||
}
|
||||
|
|
@ -919,7 +919,7 @@ func TestUnpackTuple(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if reflect.DeepEqual(ret, expected) {
|
||||
if !reflect.DeepEqual(ret, expected) {
|
||||
t.Error("unexpected unpack value")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
39
appveyor.yml
39
appveyor.yml
|
|
@ -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
|
||||
|
|
@ -81,6 +81,7 @@ var (
|
|||
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
|
||||
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
|
||||
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"}
|
||||
TooDeepReorg = &EngineAPIError{code: -38006, msg: "Too deep reorg"}
|
||||
|
||||
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
|
||||
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
|
|||
if data.BaseFeePerGas != nil && (data.BaseFeePerGas.Sign() == -1 || data.BaseFeePerGas.BitLen() > 256) {
|
||||
return nil, fmt.Errorf("invalid baseFeePerGas: %v", data.BaseFeePerGas)
|
||||
}
|
||||
var blobHashes = make([]common.Hash, 0, len(txs))
|
||||
var blobHashes = make([]common.Hash, 0, len(versionedHashes))
|
||||
for _, tx := range txs {
|
||||
blobHashes = append(blobHashes, tx.BlobHashes()...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,49 +5,49 @@
|
|||
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
|
||||
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
|
||||
|
||||
# version:golang 1.25.9
|
||||
# version:golang 1.25.10
|
||||
# https://go.dev/dl/
|
||||
0ec9ef8ebcea097aac37decae9f09a7218b451cd96be7d6ed513d8e4bcf909cf go1.25.9.src.tar.gz
|
||||
b9ede6378a8f8d3d22bf52e68beb69ef7abdb65929ab2456020383002da15846 go1.25.9.aix-ppc64.tar.gz
|
||||
92cb78fba4796e218c1accb0ea0a214ef2094c382049a244ad6505505d015fbe go1.25.9.darwin-amd64.tar.gz
|
||||
9528be7329b9770631a6bd09ca2f3a73ed7332bec01d87435e75e92d8f130363 go1.25.9.darwin-arm64.tar.gz
|
||||
918e44a471c5524caa52f74185064240d5eb343aa8023d604776511fc7adffa6 go1.25.9.dragonfly-amd64.tar.gz
|
||||
2d67dbdfd09c6fcaa0e64485367ef43b8837ea200c663d6417183237bcddf83d go1.25.9.freebsd-386.tar.gz
|
||||
9152d0c0badbfeb0c0e148e47c12bec28099d8cf2db60958810c879e0b679d07 go1.25.9.freebsd-amd64.tar.gz
|
||||
437dca59604ad4a806a6a88e3d7ec1cd98ac9b402a3671629f4e553dd8b9888f go1.25.9.freebsd-arm.tar.gz
|
||||
4c0fe53977412036fc8081e8d0992bbaabe4d3e1926137271ba11c2f5753300f go1.25.9.freebsd-arm64.tar.gz
|
||||
d6087cdd1c084bd186132f29e0d032852a745f3c7619003d0fd5612c1fa58c8a go1.25.9.freebsd-riscv64.tar.gz
|
||||
f82e49037e195cb62beae6a6ad83497157b2af5a01bad2f1dcb65df41080aabb go1.25.9.illumos-amd64.tar.gz
|
||||
1e14a73bc2b19e370e0d4c57ba87aabfe8aef1e435e14d246742d48a13254f36 go1.25.9.linux-386.tar.gz
|
||||
00859d7bd6defe8bf84d9db9e57b9a4467b2887c18cd93ae7460e713db774bc1 go1.25.9.linux-amd64.tar.gz
|
||||
ec342e7389b7f489564ed5463c63b16cf8040023dabc7861256677165a8c0e2b go1.25.9.linux-arm64.tar.gz
|
||||
7d4f0d266d871301e08ef4ac31c56e66048688893b2848392e5c600276351ee8 go1.25.9.linux-armv6l.tar.gz
|
||||
f3460d901a14496bc609636e4accf9110ee1869d41c64af7e29cd567cffcf49b go1.25.9.linux-loong64.tar.gz
|
||||
1da96ea449382ff96c09c55cee74815324e01d687d5ac6d2ade58244b8574306 go1.25.9.linux-mips.tar.gz
|
||||
311a7f5f01f9a4bd51288b575eb619dc8e28e1fbc0cd78256a428b3ca668ff01 go1.25.9.linux-mips64.tar.gz
|
||||
0b4edaf9e2ba3f0a079547effda70ec6a4b51a6ca3271a1147652c87ebcf3735 go1.25.9.linux-mips64le.tar.gz
|
||||
42667340df264896f20b12261429d954e736e9772ab83ba289e68c30cf6f9628 go1.25.9.linux-mipsle.tar.gz
|
||||
b9cbb3a4894b5aca6966c23452608435e8535278ef019b18d8898fbbfab67e74 go1.25.9.linux-ppc64.tar.gz
|
||||
b0c41c7da1fc8d39020d65296a0dc54167afd9f76d67064e22c31ce3d839a739 go1.25.9.linux-ppc64le.tar.gz
|
||||
2a630be8f854177c13e5fa75f7812c721369ecb9bd6e4c0fb1bd1c708d08b37c go1.25.9.linux-riscv64.tar.gz
|
||||
0cf55136ac7eaccfc36d849054f849510ea289c2d959ffbed7b3866b4f484d17 go1.25.9.linux-s390x.tar.gz
|
||||
eaf8167ff10a6a3e5dd304ef5f2e020b3a7379e76fa1011dc49c895800bf367c go1.25.9.netbsd-386.tar.gz
|
||||
3cc6a861e62e23feae660984e0f2f14a2efb5d1f655900afee1d51af98919ae4 go1.25.9.netbsd-amd64.tar.gz
|
||||
c2c44dca10e882c30553f4aa2ab8f6722b670fb12882378c8f461a9105d40188 go1.25.9.netbsd-arm.tar.gz
|
||||
f301b71a8ec448053a5d2597df2e178120204bc9a33266c81600dd5d020a61b4 go1.25.9.netbsd-arm64.tar.gz
|
||||
c4543b7fdef9707b4896810c69b4160a43ecec210af45c300f3abd78aa0c9e72 go1.25.9.openbsd-386.tar.gz
|
||||
37275325e314f5ab7cf8ae65c4efc7cbfdaf20b41c6849549739b57a3ac97544 go1.25.9.openbsd-amd64.tar.gz
|
||||
f9c05b6b315e979ecdd47354dd287c01708d6a88dc6ae7af74c84df8fa00df94 go1.25.9.openbsd-arm.tar.gz
|
||||
4e999f42cf959ff95ca84af1ea1db3771000f5e57e157904bc2ffc72c75e29a2 go1.25.9.openbsd-arm64.tar.gz
|
||||
0c7fa6c7c2b1cc13ad32fa94fc31273b4adf39c1e0f0e5dcedac158ff526af3f go1.25.9.openbsd-ppc64.tar.gz
|
||||
347b33953a4b6e8df17719296f360f60878fe48a2d482ceb3637a3dfd4950065 go1.25.9.openbsd-riscv64.tar.gz
|
||||
889f77d567c06832e0d332fe2458653dc66d43cded7ddbca6f72ce0ca60029cc go1.25.9.plan9-386.tar.gz
|
||||
978b1f931fadec2f2516237d2649ee845d93c8eaf47dd196cfd8d26c7b2706a1 go1.25.9.plan9-amd64.tar.gz
|
||||
30b9565e5ad0a212fe00990ead700c751b416eb2ef8d7c91a204945a7ff83a48 go1.25.9.plan9-arm.tar.gz
|
||||
9e9125ff84ab3c3522ec758cab9540a17e9cba12bfcc34b6bf556cb89b522591 go1.25.9.solaris-amd64.tar.gz
|
||||
bf40515f5f4d834fa9ead31ff75581e61a38ac27bf49840b95c5c998d321c0f6 go1.25.9.windows-386.zip
|
||||
a7a710e225467b34e9e09fb432b829c86c9b2da5821ee5418f7eb2e8ae1a22cc go1.25.9.windows-amd64.zip
|
||||
33cd73cf1b3ceee655ef71bc96e94006c02ae3c617fdd67ac9be3dfae3957449 go1.25.9.windows-arm64.zip
|
||||
20cf04a92e5af99748e341bc8996fa28090c9ac98765fa115ec5ddf41d7af41d go1.25.10.src.tar.gz
|
||||
a194e767c2ab4216a60acc068b9dbe6bf4fae05c14bb52d6bbdcb5b3ea521308 go1.25.10.aix-ppc64.tar.gz
|
||||
52321165a3146cd91865ef98371506a846ed4dc4f9f1c9323e5ad90d2a411e06 go1.25.10.darwin-amd64.tar.gz
|
||||
795691a425de7e7cdba3544f354dcd2cebcf52e87dc6898193878f34eb6d634f go1.25.10.darwin-arm64.tar.gz
|
||||
e37b4544ba9e9e9a7ab2ed3116b3fc4d39a88da854baa5a566d9d6d3a9de7d4c go1.25.10.dragonfly-amd64.tar.gz
|
||||
2a70d1fdabab637aa442ca94599a56e381238efa20cb995d5433b8579bfe482c go1.25.10.freebsd-386.tar.gz
|
||||
9cdf522d87d47d82fec4a313cc4f8c3c94a7770426e8d443e4150a1f330cba71 go1.25.10.freebsd-amd64.tar.gz
|
||||
6da6183633e9e59ffd9edefab68b5059c89b605596d94aaba650b1681fccd35f go1.25.10.freebsd-arm.tar.gz
|
||||
7adcefeebdd05331f4d45f1ad2dddb5c53537cff6552e82f6595b3b833b95371 go1.25.10.freebsd-arm64.tar.gz
|
||||
285f80a1ace21a7d94035cd753196eeada8cacd48e6396fd116ad5eb67aea957 go1.25.10.freebsd-riscv64.tar.gz
|
||||
de7461bf0e5068a4f6e7f8713026d70516be6dbd5de5d21f9ced1c182f2f326e go1.25.10.illumos-amd64.tar.gz
|
||||
2f574f2e2e19ead5b280fec0e7af5c81b76632685f03b6ac42dfa34c4b773c52 go1.25.10.linux-386.tar.gz
|
||||
42d4f7a32316aa66591eca7e89867256057a4264451aca10570a715b3637ba70 go1.25.10.linux-amd64.tar.gz
|
||||
654da1f9b50a5d1c2a85ccf8ed405aa89c06e94d18384628bf186f7712677b08 go1.25.10.linux-arm64.tar.gz
|
||||
39f168f158e693887d3ad006168af1b1a3007b19c5993cae4d9d57f82f52aaf8 go1.25.10.linux-armv6l.tar.gz
|
||||
05401fe5ea50ad2bafb9c797ef9bf21574b0661f19ef4d0dd66af8a0fb7323f3 go1.25.10.linux-loong64.tar.gz
|
||||
d5bc2d6155d394a3aae41f21eb7c60da5595a6147aa0f30ed6b27da25e06c3f7 go1.25.10.linux-mips.tar.gz
|
||||
8c64e7493e5953c3ba3153487d2fddd7f8ed142392c77f138e6792a6c1930db4 go1.25.10.linux-mips64.tar.gz
|
||||
bd53aa2d558b7c1eadfc6bf01132e1859203a92f458ed7ba75b7f3230f14b095 go1.25.10.linux-mips64le.tar.gz
|
||||
120b254e2e2980bb06687175db5c4064a85696c53001dc9f59934ad18f74a6bc go1.25.10.linux-mipsle.tar.gz
|
||||
8a6acb21295b0ec974a44608361920ea8dbff5666631a6f556bd7d5f1d56535f go1.25.10.linux-ppc64.tar.gz
|
||||
778925fdcdf9a272f823d147fad51545c3334b7ccd8652b2ccaaf2b01800280a go1.25.10.linux-ppc64le.tar.gz
|
||||
b4f04ad0db48bcfea946db5323919cd21034e0bd2821a557dacd29c1b1013a4b go1.25.10.linux-riscv64.tar.gz
|
||||
936b953e43921a64c12da871f76871ebbeb6d2092a7b8bdc307f5246f3c662cc go1.25.10.linux-s390x.tar.gz
|
||||
061470e0bc7132146a5925a3cc28d5bc498eb1b1ff09dedcfaae10f781ff2274 go1.25.10.netbsd-386.tar.gz
|
||||
63b2d50d7f8f269a9c82d42a4060e90cffb7f9102299818bb071b067aac8da8f go1.25.10.netbsd-amd64.tar.gz
|
||||
c35129f68796526aa4dc4b6f481e2d995ef312aedadc88b659b945cc00e1f8f0 go1.25.10.netbsd-arm.tar.gz
|
||||
2f541da4e2b298154d992d1f11bbb38c89d0821d91cc50a46776d42bb5e63bca go1.25.10.netbsd-arm64.tar.gz
|
||||
2d42e569b07f1b99fdbfd008e7c22f967d165e2ce02464f46818fbed2aec43f5 go1.25.10.openbsd-386.tar.gz
|
||||
0ad05960e8c9f867328151308c87f938433bec8f22f6a9437a896e22169fc840 go1.25.10.openbsd-amd64.tar.gz
|
||||
099cc11473f99461c77161912740945308f08f6834980afb262c72bdc915f2d7 go1.25.10.openbsd-arm.tar.gz
|
||||
bdf3335d5008c1ddc81fa94892283e4f1fee22566f5351d4e726d9f55a67c838 go1.25.10.openbsd-arm64.tar.gz
|
||||
0933d418da0a61e0f29de717a77498f16b9b5b50dbe2205e20b2ed7fd4067f75 go1.25.10.openbsd-ppc64.tar.gz
|
||||
191e6f3e75712f8c13d189d53b668e2cac6449f26474c1d86fbd04f6e9846f9c go1.25.10.openbsd-riscv64.tar.gz
|
||||
68c053c8acd76c50fc430e92f4a86110ec3d97dd03d27b9339b4eaf793caff5f go1.25.10.plan9-386.tar.gz
|
||||
42e2c46638ae22d93402e79efb40faee5c42cf7c56a01bb3ab47c6bb2512b745 go1.25.10.plan9-amd64.tar.gz
|
||||
3ef1d5838b1648da16724a07b72e839ccbd7cb8899c3e0426afd6b79d494b91c go1.25.10.plan9-arm.tar.gz
|
||||
631e3716017fbec06500a628d97e1155daec3593f0a7812c2ebfe8fc8c96b2ab go1.25.10.solaris-amd64.tar.gz
|
||||
ddc693d2d9d7cc671ebb72d1d50aa05670f95b059b7d90440611af57976871d5 go1.25.10.windows-386.zip
|
||||
ca37af2dadd8544464f1a9ca7c3886499d1cdfcb263855d0a1d71f194b2bd222 go1.25.10.windows-amd64.zip
|
||||
38be57e0398bd93673d65bcae6dc7ee3cf151d7038d0dba5c60a5153022872da go1.25.10.windows-arm64.zip
|
||||
|
||||
# version:golangci 2.10.1
|
||||
# https://github.com/golangci/golangci-lint/releases/
|
||||
|
|
|
|||
125
build/ci.go
125
build/ci.go
|
|
@ -73,21 +73,9 @@ var (
|
|||
"./cmd/keeper",
|
||||
}
|
||||
|
||||
// Files that end up in the geth*.zip archive.
|
||||
gethArchiveFiles = []string{
|
||||
"COPYING",
|
||||
executablePath("geth"),
|
||||
}
|
||||
|
||||
// Files that end up in the geth-alltools*.zip archive.
|
||||
allToolsArchiveFiles = []string{
|
||||
"COPYING",
|
||||
executablePath("abigen"),
|
||||
executablePath("evm"),
|
||||
executablePath("geth"),
|
||||
executablePath("rlpdump"),
|
||||
executablePath("clef"),
|
||||
}
|
||||
// Files that end up in the geth-alltools*.zip archive (and the NSIS installer
|
||||
// dev-tools section). Order matches the historical layout produced by ci.go.
|
||||
allToolsBinaries = []string{"abigen", "evm", "geth", "rlpdump", "clef"}
|
||||
|
||||
// Keeper build targets with their configurations
|
||||
keeperTargets = []struct {
|
||||
|
|
@ -180,13 +168,35 @@ var (
|
|||
|
||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||
|
||||
func executablePath(name string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
// executablePath returns the path to a built binary in GOBIN, applying the
|
||||
// platform-specific extension for the given target OS.
|
||||
func executablePath(name, targetOS string) string {
|
||||
if targetOS == "windows" {
|
||||
name += ".exe"
|
||||
}
|
||||
return filepath.Join(GOBIN, name)
|
||||
}
|
||||
|
||||
// gethArchiveFiles returns the file list for the geth-{platform}-{ver}.zip
|
||||
// archive, with binary paths resolved for the target OS.
|
||||
func gethArchiveFiles(targetOS string) []string {
|
||||
return []string{
|
||||
"COPYING",
|
||||
executablePath("geth", targetOS),
|
||||
}
|
||||
}
|
||||
|
||||
// allToolsArchiveFiles returns the file list for the
|
||||
// geth-alltools-{platform}-{ver}.zip archive, with binary paths resolved for
|
||||
// the target OS.
|
||||
func allToolsArchiveFiles(targetOS string) []string {
|
||||
files := []string{"COPYING"}
|
||||
for _, name := range allToolsBinaries {
|
||||
files = append(files, executablePath(name, targetOS))
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Lshortfile)
|
||||
|
||||
|
|
@ -233,6 +243,7 @@ func main() {
|
|||
func doInstall(cmdline []string) {
|
||||
var (
|
||||
dlgo = flag.Bool("dlgo", false, "Download Go and build with it")
|
||||
targetOS = flag.String("os", runtime.GOOS, "Target OS to cross build for")
|
||||
arch = flag.String("arch", "", "Architecture to cross build for")
|
||||
cc = flag.String("cc", "", "C compiler to cross build with")
|
||||
staticlink = flag.Bool("static", false, "Create statically-linked executable")
|
||||
|
|
@ -241,7 +252,7 @@ func doInstall(cmdline []string) {
|
|||
env := build.Env()
|
||||
|
||||
// Configure the toolchain.
|
||||
tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
|
||||
tc := build.GoToolchain{GOOS: *targetOS, GOARCH: *arch, CC: *cc}
|
||||
if *dlgo {
|
||||
csdb := download.MustLoadChecksums("build/checksums.txt")
|
||||
tc.Root = build.DownloadGo(csdb)
|
||||
|
|
@ -255,7 +266,7 @@ func doInstall(cmdline []string) {
|
|||
}
|
||||
|
||||
// Configure the build.
|
||||
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...)
|
||||
gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags, *targetOS)...)
|
||||
|
||||
// Show packages during build.
|
||||
gobuild.Args = append(gobuild.Args, "-v")
|
||||
|
|
@ -270,7 +281,7 @@ func doInstall(cmdline []string) {
|
|||
// Do the build!
|
||||
for _, pkg := range packages {
|
||||
args := slices.Clone(gobuild.Args)
|
||||
args = append(args, "-o", executablePath(path.Base(pkg)))
|
||||
args = append(args, "-o", executablePath(path.Base(pkg), *targetOS))
|
||||
args = append(args, pkg)
|
||||
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env})
|
||||
}
|
||||
|
|
@ -297,7 +308,13 @@ func doInstallKeeper(cmdline []string) {
|
|||
tc.GOARCH = target.GOARCH
|
||||
tc.GOOS = target.GOOS
|
||||
tc.CC = target.CC
|
||||
gobuild := tc.Go("build", buildFlags(env, true, []string{target.Tags})...)
|
||||
// An empty GOOS means "build for the host OS"; thread that through to
|
||||
// buildFlags so platform-specific linker flags are picked correctly.
|
||||
targetOS := target.GOOS
|
||||
if targetOS == "" {
|
||||
targetOS = runtime.GOOS
|
||||
}
|
||||
gobuild := tc.Go("build", buildFlags(env, true, []string{target.Tags}, targetOS)...)
|
||||
gobuild.Dir = "./cmd/keeper"
|
||||
gobuild.Args = append(gobuild.Args, "-v")
|
||||
|
||||
|
|
@ -307,14 +324,15 @@ func doInstallKeeper(cmdline []string) {
|
|||
outputName := fmt.Sprintf("keeper-%s", target.Name)
|
||||
|
||||
args := slices.Clone(gobuild.Args)
|
||||
args = append(args, "-o", executablePath(outputName))
|
||||
args = append(args, "-o", executablePath(outputName, targetOS))
|
||||
args = append(args, ".")
|
||||
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env, Dir: gobuild.Dir})
|
||||
}
|
||||
}
|
||||
|
||||
// buildFlags returns the go tool flags for building.
|
||||
func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (flags []string) {
|
||||
// buildFlags returns the go tool flags for building. targetOS is the OS we
|
||||
// are producing binaries for.
|
||||
func buildFlags(env build.Environment, staticLinking bool, buildTags []string, targetOS string) (flags []string) {
|
||||
var ld []string
|
||||
// See https://github.com/golang/go/issues/33772#issuecomment-528176001
|
||||
// We need to set --buildid to the linker here, and also pass --build-id to the
|
||||
|
|
@ -326,10 +344,10 @@ func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (
|
|||
}
|
||||
// Strip DWARF on darwin. This used to be required for certain things,
|
||||
// and there is no downside to this, so we just keep doing it.
|
||||
if runtime.GOOS == "darwin" {
|
||||
if targetOS == "darwin" {
|
||||
ld = append(ld, "-s")
|
||||
}
|
||||
if runtime.GOOS == "linux" {
|
||||
if targetOS == "linux" {
|
||||
// Enforce the stacksize to 8M, which is the case on most platforms apart from
|
||||
// alpine Linux.
|
||||
// See https://sourceware.org/binutils/docs-2.23.1/ld/Options.html#Options
|
||||
|
|
@ -682,12 +700,13 @@ func downloadProtoc(cachedir string) string {
|
|||
// Release Packaging
|
||||
func doArchive(cmdline []string) {
|
||||
var (
|
||||
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
|
||||
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
|
||||
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
|
||||
signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`)
|
||||
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
|
||||
ext string
|
||||
targetOS = flag.String("os", runtime.GOOS, "Target OS the binaries were built for")
|
||||
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
|
||||
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
|
||||
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
|
||||
signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`)
|
||||
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
|
||||
ext string
|
||||
)
|
||||
flag.CommandLine.Parse(cmdline)
|
||||
switch *atype {
|
||||
|
|
@ -701,15 +720,15 @@ func doArchive(cmdline []string) {
|
|||
|
||||
var (
|
||||
env = build.Env()
|
||||
basegeth = archiveBasename(*arch, version.Archive(env.Commit))
|
||||
basegeth = archiveBasename(*targetOS, *arch, version.Archive(env.Commit))
|
||||
geth = "geth-" + basegeth + ext
|
||||
alltools = "geth-alltools-" + basegeth + ext
|
||||
)
|
||||
maybeSkipArchive(env)
|
||||
if err := build.WriteArchive(geth, gethArchiveFiles); err != nil {
|
||||
if err := build.WriteArchive(geth, gethArchiveFiles(*targetOS)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil {
|
||||
if err := build.WriteArchive(alltools, allToolsArchiveFiles(*targetOS)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, archive := range []string{geth, alltools} {
|
||||
|
|
@ -735,7 +754,11 @@ func doKeeperArchive(cmdline []string) {
|
|||
maybeSkipArchive(env)
|
||||
files := []string{"COPYING"}
|
||||
for _, target := range keeperTargets {
|
||||
files = append(files, executablePath(fmt.Sprintf("keeper-%s", target.Name)))
|
||||
targetOS := target.GOOS
|
||||
if targetOS == "" {
|
||||
targetOS = runtime.GOOS
|
||||
}
|
||||
files = append(files, executablePath(fmt.Sprintf("keeper-%s", target.Name), targetOS))
|
||||
}
|
||||
if err := build.WriteArchive(keeper, files); err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
@ -745,8 +768,8 @@ func doKeeperArchive(cmdline []string) {
|
|||
}
|
||||
}
|
||||
|
||||
func archiveBasename(arch string, archiveVersion string) string {
|
||||
platform := runtime.GOOS + "-" + arch
|
||||
func archiveBasename(targetOS, arch, archiveVersion string) string {
|
||||
platform := targetOS + "-" + arch
|
||||
if arch == "arm" {
|
||||
platform += os.Getenv("GOARM")
|
||||
}
|
||||
|
|
@ -1209,13 +1232,13 @@ func doWindowsInstaller(cmdline []string) {
|
|||
env := build.Env()
|
||||
maybeSkipArchive(env)
|
||||
|
||||
// Aggregate binaries that are included in the installer
|
||||
// Aggregate binaries that are included in the installer.
|
||||
var (
|
||||
devTools []string
|
||||
allTools []string
|
||||
gethTool string
|
||||
)
|
||||
for _, file := range allToolsArchiveFiles {
|
||||
for _, file := range allToolsArchiveFiles("windows") {
|
||||
if file == "COPYING" { // license, copied later
|
||||
continue
|
||||
}
|
||||
|
|
@ -1252,16 +1275,24 @@ func doWindowsInstaller(cmdline []string) {
|
|||
if env.Commit != "" {
|
||||
ver[2] += "-" + env.Commit[:8]
|
||||
}
|
||||
installer, err := filepath.Abs("geth-" + archiveBasename(*arch, version.Archive(env.Commit)) + ".exe")
|
||||
installer, err := filepath.Abs("geth-" + archiveBasename("windows", *arch, version.Archive(env.Commit)) + ".exe")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to convert installer file path: %v", err)
|
||||
}
|
||||
build.MustRunCommand("makensis.exe",
|
||||
"/DOUTPUTFILE="+installer,
|
||||
"/DMAJORVERSION="+ver[0],
|
||||
"/DMINORVERSION="+ver[1],
|
||||
"/DBUILDVERSION="+ver[2],
|
||||
"/DARCH="+*arch,
|
||||
// makensis on Windows is "makensis.exe" with /D-style defines; on Linux
|
||||
// (and other Unixes) the binary is "makensis" and accepts -D.
|
||||
makensisCmd := "makensis"
|
||||
defineFlag := "-D"
|
||||
if runtime.GOOS == "windows" {
|
||||
makensisCmd = "makensis.exe"
|
||||
defineFlag = "/D"
|
||||
}
|
||||
build.MustRunCommand(makensisCmd,
|
||||
defineFlag+"OUTPUTFILE="+installer,
|
||||
defineFlag+"MAJORVERSION="+ver[0],
|
||||
defineFlag+"MINORVERSION="+ver[1],
|
||||
defineFlag+"BUILDVERSION="+ver[2],
|
||||
defineFlag+"ARCH="+*arch,
|
||||
filepath.Join(*workdir, "geth.nsi"),
|
||||
)
|
||||
// Sign and publish installer.
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ func generate(c *cli.Context) error {
|
|||
code string
|
||||
err error
|
||||
)
|
||||
if c.IsSet(v2Flag.Name) {
|
||||
if c.Bool(v2Flag.Name) {
|
||||
code, err = abigen.BindV2(types, abis, bins, c.String(pkgFlag.Name), libs, aliases)
|
||||
} else {
|
||||
code, err = abigen.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases)
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ func formatAttrString(v rlp.RawValue) (string, bool) {
|
|||
|
||||
func formatAttrIP(v rlp.RawValue) (string, bool) {
|
||||
content, _, err := rlp.SplitString(v)
|
||||
if err != nil || len(content) != 4 && len(content) != 6 {
|
||||
if err != nil || len(content) != 4 && len(content) != 16 {
|
||||
return "", false
|
||||
}
|
||||
return net.IP(content).String(), true
|
||||
|
|
|
|||
|
|
@ -337,9 +337,6 @@ func checkAccumulator(e era.Era) error {
|
|||
// accumulation across the entire set and are verified at the end.
|
||||
for it.Next() {
|
||||
// 1) next() walks the block index, so we're able to implicitly verify it.
|
||||
if it.Error() != nil {
|
||||
return fmt.Errorf("error reading block %d: %w", it.Number(), it.Error())
|
||||
}
|
||||
block, receipts, err := it.BlockAndReceipts()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading block %d: %w", it.Number(), err)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package t8ntool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
stdmath "math"
|
||||
|
|
@ -331,27 +332,14 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
}
|
||||
|
||||
// Gather the execution-layer triggered requests.
|
||||
var requests [][]byte
|
||||
if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) {
|
||||
requests = [][]byte{}
|
||||
// EIP-6110
|
||||
var allLogs []*types.Log
|
||||
for _, receipt := range receipts {
|
||||
allLogs = append(allLogs, receipt.Logs...)
|
||||
}
|
||||
if err := core.ParseDepositLogs(&requests, allLogs, chainConfig); err != nil {
|
||||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
|
||||
}
|
||||
// EIP-7002
|
||||
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process withdrawal requests: %v", err))
|
||||
}
|
||||
// EIP-7251
|
||||
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process consolidation requests: %v", err))
|
||||
}
|
||||
var allLogs []*types.Log
|
||||
for _, receipt := range receipts {
|
||||
allLogs = append(allLogs, receipt.Logs...)
|
||||
}
|
||||
requests, err := core.PostExecution(context.Background(), chainConfig, vmContext.BlockNumber, vmContext.Time, allLogs, evm)
|
||||
if err != nil {
|
||||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("failed to process post-execution: %v", err))
|
||||
}
|
||||
|
||||
// Commit block
|
||||
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), chainConfig.IsCancun(vmContext.BlockNumber, vmContext.Time))
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ func Transaction(ctx *cli.Context) error {
|
|||
}
|
||||
// Check intrinsic gas
|
||||
rules := chainConfig.Rules(common.Big0, true, 0)
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
results = append(results, r)
|
||||
|
|
@ -147,7 +147,7 @@ func Transaction(ctx *cli.Context) error {
|
|||
}
|
||||
// For Prague txs, validate the floor data gas.
|
||||
if rules.IsPrague {
|
||||
floorDataGas, err := core.FloorDataGas(rules, tx.Data())
|
||||
floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
results = append(results, r)
|
||||
|
|
|
|||
|
|
@ -546,7 +546,7 @@ func BinKeys(ctx *cli.Context) error {
|
|||
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.UBTDefaults)
|
||||
defer db.Close()
|
||||
|
||||
bt, err := genBinTrieFromAlloc(alloc, db)
|
||||
bt, err := genBinTrieFromAlloc(alloc, db, triedb.UBTDefaults.BinTrieGroupDepth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating bt: %w", err)
|
||||
}
|
||||
|
|
@ -590,7 +590,7 @@ func BinTrieRoot(ctx *cli.Context) error {
|
|||
db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.UBTDefaults)
|
||||
defer db.Close()
|
||||
|
||||
bt, err := genBinTrieFromAlloc(alloc, db)
|
||||
bt, err := genBinTrieFromAlloc(alloc, db, triedb.UBTDefaults.BinTrieGroupDepth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating bt: %w", err)
|
||||
}
|
||||
|
|
@ -600,8 +600,8 @@ func BinTrieRoot(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
// TODO(@CPerezz): Should this go to `bintrie` module?
|
||||
func genBinTrieFromAlloc(alloc core.GenesisAlloc, db database.NodeDatabase) (*bintrie.BinaryTrie, error) {
|
||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, db)
|
||||
func genBinTrieFromAlloc(alloc core.GenesisAlloc, db database.NodeDatabase, groupDepth int) (*bintrie.BinaryTrie, error) {
|
||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, db, groupDepth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ func runCmd(ctx *cli.Context) error {
|
|||
// don't mutate the state!
|
||||
runtimeConfig.State = prestate.Copy()
|
||||
output, _, gasLeft, err := runtime.Create(input, &runtimeConfig)
|
||||
return output, gasLeft, err
|
||||
return output, initialGas - gasLeft, err
|
||||
}
|
||||
} else {
|
||||
if len(code) > 0 {
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ func convertToBinaryTrie(ctx *cli.Context) error {
|
|||
})
|
||||
defer destTriedb.Close()
|
||||
|
||||
binTrie, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
|
||||
binTrie, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb, ctx.Int(utils.BinTrieGroupDepthFlag.Name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create binary trie: %w", err)
|
||||
}
|
||||
|
|
@ -319,7 +319,7 @@ func commitBinaryTrie(bt *bintrie.BinaryTrie, currentRoot common.Hash, destDB *t
|
|||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
|
||||
bt, err := bintrie.NewBinaryTrie(newRoot, destDB)
|
||||
bt, err := bintrie.NewBinaryTrie(newRoot, destDB, bt.GroupDepth())
|
||||
if err != nil {
|
||||
return nil, common.Hash{}, fmt.Errorf("failed to reload binary trie: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ func TestBintrieConvert(t *testing.T) {
|
|||
})
|
||||
defer destTriedb.Close()
|
||||
|
||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
|
||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb, 8)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create binary trie: %v", err)
|
||||
}
|
||||
|
|
@ -98,7 +98,7 @@ func TestBintrieConvert(t *testing.T) {
|
|||
}
|
||||
t.Logf("Binary trie root: %x", currentRoot)
|
||||
|
||||
bt2, err := bintrie.NewBinaryTrie(currentRoot, destTriedb)
|
||||
bt2, err := bintrie.NewBinaryTrie(currentRoot, destTriedb, 8)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to reload binary trie: %v", err)
|
||||
}
|
||||
|
|
@ -194,7 +194,7 @@ func TestBintrieConvertDeleteSource(t *testing.T) {
|
|||
PathDB: pathdb.Defaults,
|
||||
})
|
||||
|
||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
|
||||
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb, 8)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create binary trie: %v", err)
|
||||
}
|
||||
|
|
@ -209,7 +209,7 @@ func TestBintrieConvertDeleteSource(t *testing.T) {
|
|||
}
|
||||
srcTriedb2.Close()
|
||||
|
||||
bt2, err := bintrie.NewBinaryTrie(newRoot, destTriedb)
|
||||
bt2, err := bintrie.NewBinaryTrie(newRoot, destTriedb, 8)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to reload binary trie after deletion: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ func dumpGenesis(ctx *cli.Context) error {
|
|||
var genesis *core.Genesis
|
||||
if utils.IsNetworkPreset(ctx) {
|
||||
genesis = utils.MakeGenesis(ctx)
|
||||
} else if ctx.IsSet(utils.DeveloperFlag.Name) && !ctx.IsSet(utils.DataDirFlag.Name) {
|
||||
} else if ctx.Bool(utils.DeveloperFlag.Name) && !ctx.IsSet(utils.DataDirFlag.Name) {
|
||||
genesis = core.DeveloperGenesisBlock(11_500_000, nil)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/catalyst"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/eth/syncer"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/internal/telemetry/tracesetup"
|
||||
"github.com/ethereum/go-ethereum/internal/version"
|
||||
|
|
@ -269,25 +270,28 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
|||
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)
|
||||
|
||||
// Configure GraphQL if requested.
|
||||
if ctx.IsSet(utils.GraphQLEnabledFlag.Name) {
|
||||
if ctx.Bool(utils.GraphQLEnabledFlag.Name) {
|
||||
utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node)
|
||||
}
|
||||
// Add the Ethereum Stats daemon if requested.
|
||||
if cfg.Ethstats.URL != "" {
|
||||
utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
|
||||
}
|
||||
|
||||
// Configure synchronization override service
|
||||
var synctarget common.Hash
|
||||
syncConfig := syncer.Config{
|
||||
ExitWhenSynced: ctx.Bool(utils.ExitWhenSyncedFlag.Name),
|
||||
}
|
||||
if ctx.IsSet(utils.SyncTargetFlag.Name) {
|
||||
target := ctx.String(utils.SyncTargetFlag.Name)
|
||||
if !common.IsHexHash(target) {
|
||||
utils.Fatalf("sync target hash is not a valid hex hash: %s", target)
|
||||
}
|
||||
synctarget = common.HexToHash(target)
|
||||
syncConfig.TargetBlock = common.HexToHash(target)
|
||||
}
|
||||
utils.RegisterSyncOverrideService(stack, eth, synctarget, ctx.Bool(utils.ExitWhenSyncedFlag.Name))
|
||||
utils.RegisterSyncOverrideService(stack, eth, syncConfig)
|
||||
|
||||
if ctx.IsSet(utils.DeveloperFlag.Name) {
|
||||
if ctx.Bool(utils.DeveloperFlag.Name) {
|
||||
// Start dev mode.
|
||||
simBeacon, err := catalyst.NewSimulatedBeacon(ctx.Uint64(utils.DeveloperPeriodFlag.Name), cfg.Eth.Miner.PendingFeeRecipient, eth)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -22,13 +22,10 @@ import (
|
|||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/console/prompt"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
|
|
@ -95,6 +92,7 @@ var (
|
|||
utils.StateHistoryFlag,
|
||||
utils.TrienodeHistoryFlag,
|
||||
utils.TrienodeHistoryFullValueCheckpointFlag,
|
||||
utils.BinTrieGroupDepthFlag,
|
||||
utils.LightKDFFlag,
|
||||
utils.EthRequiredBlocksFlag,
|
||||
utils.LegacyWhitelistFlag, // deprecated
|
||||
|
|
@ -386,28 +384,4 @@ func startNode(ctx *cli.Context, stack *node.Node, isConsole bool) {
|
|||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Spawn a standalone goroutine for status synchronization monitoring,
|
||||
// close the node when synchronization is complete if user required.
|
||||
if ctx.Bool(utils.ExitWhenSyncedFlag.Name) {
|
||||
go func() {
|
||||
sub := stack.EventMux().Subscribe(downloader.DoneEvent{})
|
||||
defer sub.Unsubscribe()
|
||||
for {
|
||||
event := <-sub.Chan()
|
||||
if event == nil {
|
||||
continue
|
||||
}
|
||||
done, ok := event.Data.(downloader.DoneEvent)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute {
|
||||
log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(),
|
||||
"age", common.PrettyAge(timestamp))
|
||||
stack.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -297,6 +297,12 @@ var (
|
|||
Value: ethconfig.Defaults.EnableStateSizeTracking,
|
||||
Category: flags.StateCategory,
|
||||
}
|
||||
BinTrieGroupDepthFlag = &cli.IntFlag{
|
||||
Name: "bintrie.groupdepth",
|
||||
Usage: "Number of levels per serialized group in binary trie (1-8, default 5). Lower values create smaller groups with more nodes.",
|
||||
Value: 5,
|
||||
Category: flags.StateCategory,
|
||||
}
|
||||
StateHistoryFlag = &cli.Uint64Flag{
|
||||
Name: "history.state",
|
||||
Usage: "Number of recent blocks to retain state history for, only relevant in state.scheme=path (default = 90,000 blocks, 0 = entire chain)",
|
||||
|
|
@ -1817,6 +1823,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
if ctx.IsSet(TrienodeHistoryFullValueCheckpointFlag.Name) {
|
||||
cfg.NodeFullValueCheckpoint = uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name))
|
||||
}
|
||||
if ctx.IsSet(BinTrieGroupDepthFlag.Name) {
|
||||
cfg.BinTrieGroupDepth = ctx.Int(BinTrieGroupDepthFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(StateSchemeFlag.Name) {
|
||||
cfg.StateScheme = ctx.String(StateSchemeFlag.Name)
|
||||
}
|
||||
|
|
@ -2228,13 +2237,13 @@ func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconf
|
|||
}
|
||||
|
||||
// RegisterSyncOverrideService adds the synchronization override service into node.
|
||||
func RegisterSyncOverrideService(stack *node.Node, eth *eth.Ethereum, target common.Hash, exitWhenSynced bool) {
|
||||
if target != (common.Hash{}) {
|
||||
log.Info("Registered sync override service", "hash", target, "exitWhenSynced", exitWhenSynced)
|
||||
func RegisterSyncOverrideService(stack *node.Node, eth *eth.Ethereum, config syncer.Config) {
|
||||
if config.TargetBlock != (common.Hash{}) {
|
||||
log.Info("Registered sync override service", "hash", config.TargetBlock, "exitWhenSynced", config.ExitWhenSynced)
|
||||
} else {
|
||||
log.Info("Registered sync override service")
|
||||
}
|
||||
syncer.Register(stack, eth, target, exitWhenSynced)
|
||||
syncer.Register(stack, eth, config)
|
||||
}
|
||||
|
||||
// SetupMetrics configures the metrics system.
|
||||
|
|
@ -2433,6 +2442,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
|||
StateHistory: ctx.Uint64(StateHistoryFlag.Name),
|
||||
TrienodeHistory: ctx.Int64(TrienodeHistoryFlag.Name),
|
||||
NodeFullValueCheckpoint: uint32(ctx.Uint(TrienodeHistoryFullValueCheckpointFlag.Name)),
|
||||
BinTrieGroupDepth: ctx.Int(BinTrieGroupDepthFlag.Name),
|
||||
|
||||
// Disable transaction indexing/unindexing.
|
||||
TxLookupLimit: -1,
|
||||
|
|
|
|||
|
|
@ -204,6 +204,10 @@ func (b *Big) ToInt() *big.Int {
|
|||
return (*big.Int)(b)
|
||||
}
|
||||
|
||||
func (b *Big) ToUint256() (*uint256.Int, bool) {
|
||||
return uint256.FromBig((*big.Int)(b))
|
||||
}
|
||||
|
||||
// String returns the hex encoding of b.
|
||||
func (b *Big) String() string {
|
||||
return EncodeBig(b.ToInt())
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
|
|||
data := make([]byte, nbytes)
|
||||
return func(i int, gen *BlockGen) {
|
||||
toaddr := common.Address{}
|
||||
cost, _ := IntrinsicGas(data, nil, nil, false, false, false, false)
|
||||
cost, _ := IntrinsicGas(data, nil, nil, false, false, false, false, false)
|
||||
signer := gen.Signer()
|
||||
gasPrice := big.NewInt(0)
|
||||
if gen.header.BaseFee != nil {
|
||||
|
|
|
|||
|
|
@ -63,12 +63,12 @@ var (
|
|||
func TestProcessUBT(t *testing.T) {
|
||||
var (
|
||||
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
|
||||
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true)
|
||||
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true, false)
|
||||
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
|
||||
// will not contain that copied data.
|
||||
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
|
||||
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
|
||||
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true)
|
||||
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true, false)
|
||||
signer = types.LatestSigner(testUBTChainConfig)
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
|
||||
|
|
@ -92,6 +92,7 @@ func TestProcessUBT(t *testing.T) {
|
|||
// genesis := gspec.MustCommit(bcdb, triedb)
|
||||
options := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
||||
options.SnapshotLimit = 0
|
||||
options.BinTrieGroupDepth = triedb.DefaultBinTrieGroupDepth
|
||||
blockchain, _ := NewBlockChain(bcdb, gspec, beacon.New(ethash.NewFaker()), options)
|
||||
defer blockchain.Stop()
|
||||
|
||||
|
|
@ -218,6 +219,7 @@ func TestProcessParentBlockHash(t *testing.T) {
|
|||
t.Run("UBT", func(t *testing.T) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
cacheConfig := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
||||
cacheConfig.BinTrieGroupDepth = triedb.DefaultBinTrieGroupDepth
|
||||
cacheConfig.SnapshotLimit = 0
|
||||
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true))
|
||||
statedb, _ := state.New(types.EmptyBinaryHash, state.NewDatabase(triedb, nil))
|
||||
|
|
|
|||
|
|
@ -170,9 +170,10 @@ type BlockChainConfig struct {
|
|||
TrieNoAsyncFlush bool // Whether the asynchronous buffer flushing is disallowed
|
||||
TrieJournalDirectory string // Directory path to the journal used for persisting trie data across node restarts
|
||||
|
||||
Preimages bool // Whether to store preimage of trie key to the disk
|
||||
StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top
|
||||
ArchiveMode bool // Whether to enable the archive mode
|
||||
Preimages bool // Whether to store preimage of trie key to the disk
|
||||
StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top
|
||||
ArchiveMode bool // Whether to enable the archive mode
|
||||
BinTrieGroupDepth int // Number of levels per serialized group in binary trie (1-8)
|
||||
|
||||
// Number of blocks from the chain head for which state histories are retained.
|
||||
// If set to 0, all state histories across the entire chain will be retained;
|
||||
|
|
@ -260,8 +261,9 @@ func (cfg BlockChainConfig) WithNoAsyncFlush(on bool) *BlockChainConfig {
|
|||
// triedbConfig derives the configures for trie database.
|
||||
func (cfg *BlockChainConfig) triedbConfig(isUBT bool) *triedb.Config {
|
||||
config := &triedb.Config{
|
||||
Preimages: cfg.Preimages,
|
||||
IsUBT: isUBT,
|
||||
Preimages: cfg.Preimages,
|
||||
IsUBT: isUBT,
|
||||
BinTrieGroupDepth: cfg.BinTrieGroupDepth,
|
||||
}
|
||||
if cfg.StateScheme == rawdb.HashScheme {
|
||||
config.HashDB = &hashdb.Config{
|
||||
|
|
@ -1186,6 +1188,7 @@ func (bc *BlockChain) SnapSyncComplete(hash common.Hash) error {
|
|||
}
|
||||
|
||||
// If all checks out, manually set the head block.
|
||||
rawdb.WriteHeadBlockHash(bc.db, hash)
|
||||
bc.currentBlock.Store(block.Header())
|
||||
headBlockGauge.Update(int64(block.NumberU64()))
|
||||
|
||||
|
|
@ -2596,8 +2599,13 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
|
|||
blockReorgAddMeter.Mark(int64(len(newChain)))
|
||||
} else {
|
||||
// len(newChain) == 0 && len(oldChain) > 0
|
||||
// rewind the canonical chain to a lower point.
|
||||
log.Error("Impossible reorg, please file an issue", "oldnum", oldHead.Number, "oldhash", oldHead.Hash(), "oldblocks", len(oldChain), "newnum", newHead.Number, "newhash", newHead.Hash(), "newblocks", len(newChain))
|
||||
// Rewind the canonical chain to a lower point. In EPBs we can reorg to
|
||||
// a parent of the head within 32 blocks.
|
||||
if len(oldChain) > 32 {
|
||||
log.Error("Impossible reorg, please file an issue", "oldnum", oldHead.Number, "oldhash", oldHead.Hash(), "oldblocks", len(oldChain))
|
||||
} else {
|
||||
log.Info("Shorten chain", "del", len(oldChain), "number", oldHead.Number, "hash", oldHead.Hash())
|
||||
}
|
||||
}
|
||||
// Acquire the tx-lookup lock before mutation. This step is essential
|
||||
// as the txlookups should be changed atomically, and all subsequent
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
|
|
@ -314,28 +315,17 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) {
|
|||
// off the statedb before executing the system calls.
|
||||
statedb = statedb.Copy()
|
||||
}
|
||||
var blockLogs []*types.Log
|
||||
for _, r := range b.receipts {
|
||||
blockLogs = append(blockLogs, r.Logs...)
|
||||
}
|
||||
// TODO use the shared EVM throughout the entire generation cycle
|
||||
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
|
||||
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
|
||||
|
||||
if b.cm.config.IsPrague(b.header.Number, b.header.Time) {
|
||||
requests = [][]byte{}
|
||||
// EIP-6110 deposits
|
||||
var blockLogs []*types.Log
|
||||
for _, r := range b.receipts {
|
||||
blockLogs = append(blockLogs, r.Logs...)
|
||||
}
|
||||
if err := ParseDepositLogs(&requests, blockLogs, b.cm.config); err != nil {
|
||||
panic(fmt.Sprintf("failed to parse deposit log: %v", err))
|
||||
}
|
||||
// create EVM for system calls
|
||||
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
|
||||
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
|
||||
// EIP-7002
|
||||
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
panic(fmt.Sprintf("could not process withdrawal requests: %v", err))
|
||||
}
|
||||
// EIP-7251
|
||||
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
panic(fmt.Sprintf("could not process consolidation requests: %v", err))
|
||||
}
|
||||
requests, err := PostExecution(context.Background(), b.cm.config, b.header.Number, b.header.Time, blockLogs, evm)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to run post-execution: %v", err))
|
||||
}
|
||||
return requests
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
|||
func NewEVMTxContext(msg *Message) vm.TxContext {
|
||||
ctx := vm.TxContext{
|
||||
Origin: msg.From,
|
||||
GasPrice: uint256.MustFromBig(msg.GasPrice),
|
||||
GasPrice: msg.GasPrice,
|
||||
BlobHashes: msg.BlobHashes,
|
||||
}
|
||||
return ctx
|
||||
|
|
|
|||
|
|
@ -136,8 +136,9 @@ func hashAlloc(ga *types.GenesisAlloc, isUBT bool) (common.Hash, error) {
|
|||
var config *triedb.Config
|
||||
if isUBT {
|
||||
config = &triedb.Config{
|
||||
PathDB: pathdb.Defaults,
|
||||
IsUBT: true,
|
||||
PathDB: pathdb.Defaults,
|
||||
IsUBT: true,
|
||||
BinTrieGroupDepth: triedb.UBTDefaults.BinTrieGroupDepth,
|
||||
}
|
||||
}
|
||||
// Create an ephemeral in-memory database for computing hash,
|
||||
|
|
|
|||
|
|
@ -261,9 +261,9 @@ func newDbConfig(scheme string) *triedb.Config {
|
|||
return &triedb.Config{PathDB: &config}
|
||||
}
|
||||
|
||||
func TestVerkleGenesisCommit(t *testing.T) {
|
||||
var verkleTime uint64 = 0
|
||||
verkleConfig := ¶ms.ChainConfig{
|
||||
func TestBinaryGenesisCommit(t *testing.T) {
|
||||
var ubtTime uint64 = 0
|
||||
ubtConfig := ¶ms.ChainConfig{
|
||||
ChainID: big.NewInt(1),
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
DAOForkBlock: nil,
|
||||
|
|
@ -281,11 +281,11 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
|||
ArrowGlacierBlock: big.NewInt(0),
|
||||
GrayGlacierBlock: big.NewInt(0),
|
||||
MergeNetsplitBlock: nil,
|
||||
ShanghaiTime: &verkleTime,
|
||||
CancunTime: &verkleTime,
|
||||
PragueTime: &verkleTime,
|
||||
OsakaTime: &verkleTime,
|
||||
UBTTime: &verkleTime,
|
||||
ShanghaiTime: &ubtTime,
|
||||
CancunTime: &ubtTime,
|
||||
PragueTime: &ubtTime,
|
||||
OsakaTime: &ubtTime,
|
||||
UBTTime: &ubtTime,
|
||||
TerminalTotalDifficulty: big.NewInt(0),
|
||||
EnableUBTAtGenesis: true,
|
||||
Ethash: nil,
|
||||
|
|
@ -300,8 +300,8 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
|||
|
||||
genesis := &Genesis{
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
Config: verkleConfig,
|
||||
Timestamp: verkleTime,
|
||||
Config: ubtConfig,
|
||||
Timestamp: ubtTime,
|
||||
Difficulty: big.NewInt(0),
|
||||
Alloc: types.GenesisAlloc{
|
||||
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
|
||||
|
|
@ -320,17 +320,18 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
|||
config.NoAsyncFlush = true
|
||||
|
||||
triedb := triedb.NewDatabase(db, &triedb.Config{
|
||||
IsUBT: true,
|
||||
PathDB: &config,
|
||||
IsUBT: true,
|
||||
PathDB: &config,
|
||||
BinTrieGroupDepth: triedb.DefaultBinTrieGroupDepth,
|
||||
})
|
||||
block := genesis.MustCommit(db, triedb)
|
||||
if !bytes.Equal(block.Root().Bytes(), expected) {
|
||||
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, block.Root())
|
||||
}
|
||||
|
||||
// Test that the trie is verkle
|
||||
// Test that the trie is a unified binary trie
|
||||
if !triedb.IsUBT() {
|
||||
t.Fatalf("expected trie to be verkle")
|
||||
t.Fatalf("expected trie to be a unified binary trie")
|
||||
}
|
||||
vdb := rawdb.NewTable(db, string(rawdb.VerklePrefix))
|
||||
if !rawdb.HasAccountTrieNode(vdb, nil) {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ func (db *UBTDatabase) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Rea
|
|||
|
||||
// OpenTrie opens the main account trie at a specific root hash.
|
||||
func (db *UBTDatabase) OpenTrie(root common.Hash) (Trie, error) {
|
||||
return bintrie.NewBinaryTrie(root, db.triedb)
|
||||
return bintrie.NewBinaryTrie(root, db.triedb, db.triedb.BinTrieGroupDepth())
|
||||
}
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account. In binary trie mode,
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ type ubtTrieReader struct {
|
|||
// newUBTTrieReader constructs a Unified-binary-trie reader of the specific state.
|
||||
// An error will be returned if the associated trie specified by root is not existent.
|
||||
func newUBTTrieReader(root common.Hash, db *triedb.Database) (*ubtTrieReader, error) {
|
||||
binTrie, binErr := bintrie.NewBinaryTrie(root, db)
|
||||
binTrie, binErr := bintrie.NewBinaryTrie(root, db, db.BinTrieGroupDepth())
|
||||
if binErr != nil {
|
||||
return nil, binErr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -441,7 +441,7 @@ func TestStorageIteratorTraversalValues(t *testing.T) {
|
|||
if i%8 == 0 {
|
||||
e[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 4, i)
|
||||
}
|
||||
if i > 50 || i < 85 {
|
||||
if i > 50 && i < 85 {
|
||||
f[common.Hash{i}] = fmt.Appendf(nil, "layer-%d, key %d", 5, i)
|
||||
}
|
||||
if i%64 == 0 {
|
||||
|
|
|
|||
|
|
@ -1355,7 +1355,7 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorag
|
|||
|
||||
// The reader update must be performed as the final step, otherwise,
|
||||
// the new state would not be visible before db.commit.
|
||||
s.reader, _ = s.db.Reader(s.originalRoot)
|
||||
s.reader, err = s.db.Reader(s.originalRoot)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/internal/telemetry"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// StateProcessor is a basic Processor, which takes care of transitioning
|
||||
|
|
@ -75,31 +76,21 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
if hooks := cfg.Tracer; hooks != nil {
|
||||
tracingStateDB = state.NewHookedState(statedb, hooks)
|
||||
}
|
||||
|
||||
// Mutate the block and state according to any hard-fork specs
|
||||
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 {
|
||||
misc.ApplyDAOHardFork(tracingStateDB)
|
||||
}
|
||||
var (
|
||||
context vm.BlockContext
|
||||
context = NewEVMBlockContext(header, p.chain, nil)
|
||||
signer = types.MakeSigner(config, header.Number, header.Time)
|
||||
evm = vm.NewEVM(context, tracingStateDB, config, cfg)
|
||||
)
|
||||
|
||||
// Apply pre-execution system calls.
|
||||
context = NewEVMBlockContext(header, p.chain, nil)
|
||||
evm := vm.NewEVM(context, tracingStateDB, config, cfg)
|
||||
defer evm.Release()
|
||||
if jumpDestCache != nil {
|
||||
evm.SetJumpDestCache(jumpDestCache)
|
||||
}
|
||||
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
if config.IsPrague(block.Number(), block.Time()) || config.IsUBT(block.Number(), block.Time()) {
|
||||
ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
}
|
||||
|
||||
// Run the pre-execution system calls
|
||||
PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time())
|
||||
// Iterate over and process the individual transactions
|
||||
for i, tx := range block.Transactions() {
|
||||
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
|
||||
|
|
@ -121,11 +112,11 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
allLogs = append(allLogs, receipt.Logs...)
|
||||
spanEnd(nil)
|
||||
}
|
||||
requests, err := postExecution(ctx, config, block, allLogs, evm)
|
||||
// Run the post-execution system calls
|
||||
requests, err := PostExecution(ctx, config, block.Number(), block.Time(), allLogs, evm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body())
|
||||
|
||||
|
|
@ -137,28 +128,44 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
}, nil
|
||||
}
|
||||
|
||||
// postExecution processes the post-execution system calls if Prague is enabled.
|
||||
func postExecution(ctx context.Context, config *params.ChainConfig, block *types.Block, allLogs []*types.Log, evm *vm.EVM) (requests [][]byte, err error) {
|
||||
// PreExecution processes pre-execution system calls.
|
||||
func PreExecution(ctx context.Context, beaconRoot *common.Hash, parent common.Hash, config *params.ChainConfig, evm *vm.EVM, number *big.Int, time uint64) {
|
||||
_, _, spanEnd := telemetry.StartSpan(ctx, "core.preExecution")
|
||||
defer spanEnd(nil)
|
||||
|
||||
// EIP-4788
|
||||
if beaconRoot != nil {
|
||||
ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
// EIP-2935
|
||||
if config.IsPrague(number, time) || config.IsUBT(number, time) {
|
||||
ProcessParentBlockHash(parent, evm)
|
||||
}
|
||||
}
|
||||
|
||||
// PostExecution processes post-execution system calls when Prague is enabled.
|
||||
// If Prague is not activated, it returns null requests to differentiate from
|
||||
// empty requests.
|
||||
func PostExecution(ctx context.Context, config *params.ChainConfig, number *big.Int, time uint64, allLogs []*types.Log, evm *vm.EVM) (requests [][]byte, err error) {
|
||||
_, _, spanEnd := telemetry.StartSpan(ctx, "core.postExecution")
|
||||
defer spanEnd(&err)
|
||||
|
||||
// Read requests if Prague is enabled.
|
||||
if config.IsPrague(block.Number(), block.Time()) {
|
||||
if config.IsPrague(number, time) {
|
||||
requests = [][]byte{}
|
||||
// EIP-6110
|
||||
if err := ParseDepositLogs(&requests, allLogs, config); err != nil {
|
||||
return requests, fmt.Errorf("failed to parse deposit logs: %w", err)
|
||||
return nil, fmt.Errorf("failed to parse deposit logs: %w", err)
|
||||
}
|
||||
// EIP-7002
|
||||
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
return requests, fmt.Errorf("failed to process withdrawal queue: %w", err)
|
||||
return nil, fmt.Errorf("failed to process withdrawal queue: %w", err)
|
||||
}
|
||||
// EIP-7251
|
||||
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
return requests, fmt.Errorf("failed to process consolidation queue: %w", err)
|
||||
return nil, fmt.Errorf("failed to process consolidation queue: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return requests, nil
|
||||
}
|
||||
|
||||
|
|
@ -257,9 +264,9 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
|
|||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
GasPrice: common.Big0,
|
||||
GasFeeCap: common.Big0,
|
||||
GasTipCap: common.Big0,
|
||||
GasPrice: uint256.NewInt(0),
|
||||
GasFeeCap: uint256.NewInt(0),
|
||||
GasTipCap: uint256.NewInt(0),
|
||||
To: ¶ms.BeaconRootsAddress,
|
||||
Data: beaconRoot[:],
|
||||
}
|
||||
|
|
@ -284,9 +291,9 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
|
|||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
GasPrice: common.Big0,
|
||||
GasFeeCap: common.Big0,
|
||||
GasTipCap: common.Big0,
|
||||
GasPrice: uint256.NewInt(0),
|
||||
GasFeeCap: uint256.NewInt(0),
|
||||
GasTipCap: uint256.NewInt(0),
|
||||
To: ¶ms.HistoryStorageAddress,
|
||||
Data: prevHash.Bytes(),
|
||||
}
|
||||
|
|
@ -324,9 +331,9 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
|
|||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
GasPrice: common.Big0,
|
||||
GasFeeCap: common.Big0,
|
||||
GasTipCap: common.Big0,
|
||||
GasPrice: uint256.NewInt(0),
|
||||
GasFeeCap: uint256.NewInt(0),
|
||||
GasTipCap: uint256.NewInt(0),
|
||||
To: &addr,
|
||||
}
|
||||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ func (result *ExecutionResult) Revert() []byte {
|
|||
}
|
||||
|
||||
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (vm.GasCosts, error) {
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860, isAmsterdam bool) (vm.GasCosts, error) {
|
||||
// Set the starting gas for the raw transaction
|
||||
var gas uint64
|
||||
if isContractCreation && isHomestead {
|
||||
|
|
@ -107,8 +107,32 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
}
|
||||
}
|
||||
if accessList != nil {
|
||||
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
|
||||
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
|
||||
addresses := uint64(len(accessList))
|
||||
storageKeys := uint64(accessList.StorageKeys())
|
||||
if (math.MaxUint64-gas)/params.TxAccessListAddressGas < addresses {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += addresses * params.TxAccessListAddressGas
|
||||
if (math.MaxUint64-gas)/params.TxAccessListStorageKeyGas < storageKeys {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += storageKeys * params.TxAccessListStorageKeyGas
|
||||
|
||||
// EIP-7981: access list data is charged in addition to the base charge.
|
||||
if isAmsterdam {
|
||||
const (
|
||||
addressCost = common.AddressLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
|
||||
storageKeyCost = common.HashLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
|
||||
)
|
||||
if (math.MaxUint64-gas)/addressCost < addresses {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += addresses * addressCost
|
||||
if (math.MaxUint64-gas)/storageKeyCost < storageKeys {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += storageKeys * storageKeyCost
|
||||
}
|
||||
}
|
||||
if authList != nil {
|
||||
gas += uint64(len(authList)) * params.CallNewAccountGas
|
||||
|
|
@ -117,7 +141,7 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
}
|
||||
|
||||
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
|
||||
func FloorDataGas(rules params.Rules, data []byte) (uint64, error) {
|
||||
func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList) (uint64, error) {
|
||||
var (
|
||||
tokens uint64
|
||||
tokenCost uint64
|
||||
|
|
@ -125,15 +149,41 @@ func FloorDataGas(rules params.Rules, data []byte) (uint64, error) {
|
|||
if rules.IsAmsterdam {
|
||||
// EIP-7976 changes how calldata is priced.
|
||||
// From 10/40 to 64/64 for zero/non-zero bytes.
|
||||
tokens = uint64(len(data)) * params.TxTokenPerNonZeroByte
|
||||
tokenCost = params.TxCostFloorPerToken7976
|
||||
dataLen := uint64(len(data))
|
||||
if math.MaxUint64/params.TxTokenPerNonZeroByte < dataLen {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
tokens = dataLen * params.TxTokenPerNonZeroByte
|
||||
|
||||
// EIP-7981 adds additional tokens for every entry in the accesslist
|
||||
const addressTokenCost = uint64(common.AddressLength) * params.TxTokenPerNonZeroByte
|
||||
addresses := uint64(len(accessList))
|
||||
if (math.MaxUint64-tokens)/addressTokenCost < addresses {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
tokens += addresses * addressTokenCost
|
||||
|
||||
const storageKeyTokenCost = uint64(common.HashLength) * params.TxTokenPerNonZeroByte
|
||||
storageKeys := uint64(accessList.StorageKeys())
|
||||
if (math.MaxUint64-tokens)/storageKeyTokenCost < storageKeys {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
tokens += storageKeys * storageKeyTokenCost
|
||||
} else {
|
||||
var (
|
||||
z = uint64(bytes.Count(data, []byte{0}))
|
||||
nz = uint64(len(data)) - z
|
||||
)
|
||||
// Pre-Amsterdam
|
||||
tokens = nz*params.TxTokenPerNonZeroByte + z
|
||||
if math.MaxUint64/params.TxTokenPerNonZeroByte < nz {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
tokens = nz * params.TxTokenPerNonZeroByte
|
||||
if math.MaxUint64-tokens < z {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
tokens += z
|
||||
tokenCost = params.TxCostFloorPerToken
|
||||
}
|
||||
|
||||
|
|
@ -160,14 +210,14 @@ type Message struct {
|
|||
To *common.Address
|
||||
From common.Address
|
||||
Nonce uint64
|
||||
Value *big.Int
|
||||
Value *uint256.Int
|
||||
GasLimit uint64
|
||||
GasPrice *big.Int
|
||||
GasFeeCap *big.Int
|
||||
GasTipCap *big.Int
|
||||
GasPrice *uint256.Int
|
||||
GasFeeCap *uint256.Int
|
||||
GasTipCap *uint256.Int
|
||||
Data []byte
|
||||
AccessList types.AccessList
|
||||
BlobGasFeeCap *big.Int
|
||||
BlobGasFeeCap *uint256.Int
|
||||
BlobHashes []common.Hash
|
||||
SetCodeAuthorizations []types.SetCodeAuthorization
|
||||
|
||||
|
|
@ -188,32 +238,64 @@ type Message struct {
|
|||
|
||||
// TransactionToMessage converts a transaction into a Message.
|
||||
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
|
||||
from, err := types.Sender(s, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gasPrice, overflow := uint256.FromBig(tx.GasPrice())
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
|
||||
from.Hex(), tx.GasPrice().BitLen())
|
||||
}
|
||||
txGasFeeCap := tx.GasFeeCap()
|
||||
gasFeeCap, overflow := uint256.FromBig(txGasFeeCap)
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
|
||||
from.Hex(), tx.GasFeeCap().BitLen())
|
||||
}
|
||||
txGasTipCap := tx.GasTipCap()
|
||||
gasTipCap, overflow := uint256.FromBig(txGasTipCap)
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
|
||||
from.Hex(), tx.GasTipCap().BitLen())
|
||||
}
|
||||
value, overflow := uint256.FromBig(tx.Value())
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("value exceeds 256 bits: address %v", from.Hex())
|
||||
}
|
||||
blobGasFeeCap, overflow := uint256.FromBig(tx.BlobGasFeeCap())
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("blobGasFeeCap exceeds 256 bits: address %v", from.Hex())
|
||||
}
|
||||
|
||||
msg := &Message{
|
||||
From: from,
|
||||
Nonce: tx.Nonce(),
|
||||
GasLimit: tx.Gas(),
|
||||
GasPrice: tx.GasPrice(),
|
||||
GasFeeCap: tx.GasFeeCap(),
|
||||
GasTipCap: tx.GasTipCap(),
|
||||
GasPrice: gasPrice,
|
||||
GasFeeCap: gasFeeCap,
|
||||
GasTipCap: gasTipCap,
|
||||
To: tx.To(),
|
||||
Value: tx.Value(),
|
||||
Value: value,
|
||||
Data: tx.Data(),
|
||||
AccessList: tx.AccessList(),
|
||||
SetCodeAuthorizations: tx.SetCodeAuthorizations(),
|
||||
SkipNonceChecks: false,
|
||||
SkipTransactionChecks: false,
|
||||
BlobHashes: tx.BlobHashes(),
|
||||
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
||||
BlobGasFeeCap: blobGasFeeCap,
|
||||
}
|
||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||
if baseFee != nil {
|
||||
msg.GasPrice = msg.GasPrice.Add(msg.GasTipCap, baseFee)
|
||||
if msg.GasPrice.Cmp(msg.GasFeeCap) > 0 {
|
||||
msg.GasPrice = msg.GasFeeCap
|
||||
effectiveGasPrice := new(big.Int).Add(baseFee, txGasTipCap)
|
||||
if effectiveGasPrice.Cmp(txGasFeeCap) > 0 {
|
||||
effectiveGasPrice = txGasFeeCap
|
||||
}
|
||||
// EffectiveGasPrice is already capped by txGasFeeCap, therefore
|
||||
// the overflow check is not required.
|
||||
msg.GasPrice = uint256.MustFromBig(effectiveGasPrice)
|
||||
}
|
||||
var err error
|
||||
msg.From, err = types.Sender(s, tx)
|
||||
return msg, err
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// ApplyMessage computes the new state by applying the given message
|
||||
|
|
@ -283,32 +365,55 @@ func (st *stateTransition) to() common.Address {
|
|||
}
|
||||
|
||||
func (st *stateTransition) buyGas() error {
|
||||
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
|
||||
mgval.Mul(mgval, st.msg.GasPrice)
|
||||
balanceCheck := new(big.Int).Set(mgval)
|
||||
mgval := new(uint256.Int).SetUint64(st.msg.GasLimit)
|
||||
_, overflow := mgval.MulOverflow(mgval, st.msg.GasPrice)
|
||||
if overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
balanceCheck := new(uint256.Int).Set(mgval)
|
||||
if st.msg.GasFeeCap != nil {
|
||||
balanceCheck.SetUint64(st.msg.GasLimit)
|
||||
balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
|
||||
if _, overflow := balanceCheck.MulOverflow(balanceCheck, st.msg.GasFeeCap); overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
}
|
||||
if st.msg.Value != nil {
|
||||
if _, overflow := balanceCheck.AddOverflow(balanceCheck, st.msg.Value); overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
}
|
||||
balanceCheck.Add(balanceCheck, st.msg.Value)
|
||||
|
||||
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
|
||||
if blobGas := st.blobGasUsed(); blobGas > 0 {
|
||||
// Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap
|
||||
blobBalanceCheck := new(big.Int).SetUint64(blobGas)
|
||||
blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap)
|
||||
balanceCheck.Add(balanceCheck, blobBalanceCheck)
|
||||
blobBalanceCheck := new(uint256.Int).SetUint64(blobGas)
|
||||
if _, overflow := blobBalanceCheck.MulOverflow(blobBalanceCheck, st.msg.BlobGasFeeCap); overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
if _, overflow := balanceCheck.AddOverflow(balanceCheck, blobBalanceCheck); overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
// Pay for blobGasUsed * actual blob fee
|
||||
blobFee := new(big.Int).SetUint64(blobGas)
|
||||
blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee)
|
||||
mgval.Add(mgval, blobFee)
|
||||
blobBaseFee, overflow := uint256.FromBig(st.evm.Context.BlobBaseFee)
|
||||
if overflow {
|
||||
return fmt.Errorf("invalid blobBaseFee: %v", st.evm.Context.BlobBaseFee)
|
||||
}
|
||||
blobFee := new(uint256.Int).SetUint64(blobGas)
|
||||
|
||||
// In practice, overflow checking is unnecessary, as blobBaseFee cannot exceed
|
||||
// BlobGasFeeCap. However, in eth_call it is still possible for users to specify
|
||||
// an excessively large blob base fee and bypass the blob base fee validation.
|
||||
_, overflow = blobFee.MulOverflow(blobFee, blobBaseFee)
|
||||
if overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
_, overflow = mgval.AddOverflow(mgval, blobFee)
|
||||
if overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
}
|
||||
}
|
||||
balanceCheckU256, overflow := uint256.FromBig(balanceCheck)
|
||||
if overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
|
||||
if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
|
||||
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
|
||||
}
|
||||
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
|
||||
|
|
@ -321,8 +426,7 @@ func (st *stateTransition) buyGas() error {
|
|||
st.gasRemaining = vm.NewGasBudget(st.msg.GasLimit)
|
||||
st.initialBudget = st.gasRemaining.Copy()
|
||||
|
||||
mgvalU256, _ := uint256.FromBig(mgval)
|
||||
st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
|
||||
st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -362,21 +466,13 @@ func (st *stateTransition) preCheck() error {
|
|||
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
|
||||
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
|
||||
if !skipCheck {
|
||||
if l := msg.GasFeeCap.BitLen(); l > 256 {
|
||||
return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
|
||||
msg.From.Hex(), l)
|
||||
}
|
||||
if l := msg.GasTipCap.BitLen(); l > 256 {
|
||||
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
|
||||
msg.From.Hex(), l)
|
||||
}
|
||||
if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 {
|
||||
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
|
||||
msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap)
|
||||
}
|
||||
// This will panic if baseFee is nil, but basefee presence is verified
|
||||
// as part of header validation.
|
||||
if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 {
|
||||
if msg.GasFeeCap.CmpBig(st.evm.Context.BaseFee) < 0 {
|
||||
return fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow,
|
||||
msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee)
|
||||
}
|
||||
|
|
@ -410,7 +506,7 @@ func (st *stateTransition) preCheck() error {
|
|||
if !skipCheck {
|
||||
// This will panic if blobBaseFee is nil, but blobBaseFee presence
|
||||
// is verified as part of header validation.
|
||||
if msg.BlobGasFeeCap.Cmp(st.evm.Context.BlobBaseFee) < 0 {
|
||||
if msg.BlobGasFeeCap.CmpBig(st.evm.Context.BlobBaseFee) < 0 {
|
||||
return fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow,
|
||||
msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee)
|
||||
}
|
||||
|
|
@ -462,7 +558,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
floorDataGas uint64
|
||||
)
|
||||
// Check clauses 4-5, subtract intrinsic gas if everything is correct
|
||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
|
||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -475,7 +571,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
// Gas limit suffices for the floor data cost (EIP-7623)
|
||||
if rules.IsPrague {
|
||||
floorDataGas, err = FloorDataGas(rules, msg.Data)
|
||||
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -493,9 +589,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
|
||||
// Check clause 6
|
||||
value, overflow := uint256.FromBig(msg.Value)
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
|
||||
value := msg.Value
|
||||
if value == nil {
|
||||
value = new(uint256.Int)
|
||||
}
|
||||
if !value.IsZero() && !st.evm.Context.CanTransfer(st.state, msg.From, value) {
|
||||
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
|
||||
|
|
@ -579,9 +675,12 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
effectiveTip := msg.GasPrice
|
||||
if rules.IsLondon {
|
||||
effectiveTip = new(big.Int).Sub(msg.GasPrice, st.evm.Context.BaseFee)
|
||||
baseFee, overflow := uint256.FromBig(st.evm.Context.BaseFee)
|
||||
if overflow {
|
||||
return nil, fmt.Errorf("invalid baseFee: %v", st.evm.Context.BaseFee)
|
||||
}
|
||||
effectiveTip = new(uint256.Int).Sub(msg.GasPrice, baseFee)
|
||||
}
|
||||
effectiveTipU256, _ := uint256.FromBig(effectiveTip)
|
||||
|
||||
if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
|
||||
// Skip fee payment when NoBaseFee is set and the fee fields
|
||||
|
|
@ -589,7 +688,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
// the coinbase when simulating calls.
|
||||
} else {
|
||||
fee := new(uint256.Int).SetUint64(st.gasUsed())
|
||||
fee.Mul(fee, effectiveTipU256)
|
||||
fee.Mul(fee, effectiveTip)
|
||||
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
|
||||
|
||||
// add the coinbase to the witness iff the fee is greater than 0
|
||||
|
|
@ -691,7 +790,7 @@ func (st *stateTransition) calcRefund() vm.GasBudget {
|
|||
// exchanged at the original rate.
|
||||
func (st *stateTransition) returnGas() {
|
||||
remaining := uint256.NewInt(st.gasRemaining.RegularGas)
|
||||
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
|
||||
remaining.Mul(remaining, st.msg.GasPrice)
|
||||
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
|
||||
|
||||
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining.RegularGas > 0 {
|
||||
|
|
|
|||
287
core/state_transition_test.go
Normal file
287
core/state_transition_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -116,6 +116,8 @@ const (
|
|||
announceThreshold = -1
|
||||
)
|
||||
|
||||
var errLegacyTx = errors.New("legacy transaction format")
|
||||
|
||||
// blobTxMeta is the minimal subset of types.BlobTx necessary to validate and
|
||||
// schedule the blob transactions into the following blocks. Only ever add the
|
||||
// bare minimum needed fields to keep the size down (and thus number of entries
|
||||
|
|
@ -147,28 +149,137 @@ type blobTxMeta struct {
|
|||
evictionBlobFeeJumps float64 // Worse blob fee (converted to fee jumps) across all previous nonces
|
||||
}
|
||||
|
||||
// newBlobTxMeta retrieves the indexed metadata fields from a blob transaction
|
||||
// and assembles a helper struct to track in memory.
|
||||
// Requires the transaction to have a sidecar (or that we introduce a special version tag for no-sidecar).
|
||||
func newBlobTxMeta(id uint64, size uint64, storageSize uint32, tx *types.Transaction) *blobTxMeta {
|
||||
if tx.BlobTxSidecar() == nil {
|
||||
// This should never happen, as the pool only admits blob transactions with a sidecar
|
||||
// blobTxForPool is the storage representation of a blob transaction in the
|
||||
// blobpool.
|
||||
type blobTxForPool struct {
|
||||
Tx *types.Transaction // tx without sidecar
|
||||
Version byte
|
||||
Commitments []kzg4844.Commitment
|
||||
Proofs []kzg4844.Proof
|
||||
Blobs []kzg4844.Blob
|
||||
}
|
||||
|
||||
// Sidecar returns BlobTxSidecar of ptx.
|
||||
func (ptx *blobTxForPool) Sidecar() *types.BlobTxSidecar {
|
||||
return types.NewBlobTxSidecar(ptx.Version, ptx.Blobs, ptx.Commitments, ptx.Proofs)
|
||||
}
|
||||
|
||||
// ApplySidecar copies the sidecar's fields into the flat fields.
|
||||
func (ptx *blobTxForPool) ApplySidecar(sc *types.BlobTxSidecar) {
|
||||
ptx.Version = sc.Version
|
||||
ptx.Commitments = sc.Commitments
|
||||
ptx.Proofs = sc.Proofs
|
||||
ptx.Blobs = sc.Blobs
|
||||
}
|
||||
|
||||
// TxSize returns the transaction size on the network without
|
||||
// reconstructing the transaction.
|
||||
func (ptx *blobTxForPool) TxSize() uint64 {
|
||||
var blobs, commitments, proofs uint64
|
||||
for i := range ptx.Blobs {
|
||||
blobs += rlp.BytesSize(ptx.Blobs[i][:])
|
||||
}
|
||||
for i := range ptx.Commitments {
|
||||
commitments += rlp.BytesSize(ptx.Commitments[i][:])
|
||||
}
|
||||
for i := range ptx.Proofs {
|
||||
proofs += rlp.BytesSize(ptx.Proofs[i][:])
|
||||
}
|
||||
return ptx.Tx.Size() + rlp.ListSize(rlp.ListSize(blobs)+rlp.ListSize(commitments)+rlp.ListSize(proofs))
|
||||
}
|
||||
|
||||
// ToTx reconstructs a full Transaction with the sidecar attached.
|
||||
func (ptx *blobTxForPool) ToTx() *types.Transaction {
|
||||
return ptx.Tx.WithBlobTxSidecar(ptx.Sidecar())
|
||||
}
|
||||
|
||||
// newBlobTxForPool decomposes a blob transaction into blobTxForPool type.
|
||||
func newBlobTxForPool(tx *types.Transaction) *blobTxForPool {
|
||||
sc := tx.BlobTxSidecar()
|
||||
if sc == nil {
|
||||
panic("missing blob tx sidecar")
|
||||
}
|
||||
return &blobTxForPool{
|
||||
Tx: tx.WithoutBlobTxSidecar(),
|
||||
Version: sc.Version,
|
||||
Commitments: sc.Commitments,
|
||||
Proofs: sc.Proofs,
|
||||
Blobs: sc.Blobs,
|
||||
}
|
||||
}
|
||||
|
||||
// encodeForNetwork transforms stored blobTxForPool RLP into the standard
|
||||
// network transaction encoding. This is used for getRLP.
|
||||
//
|
||||
// Stored RLP: [type_byte || tx_fields, version, [comms], [proofs], [blobs]]
|
||||
// V0: type_byte || rlp([tx_fields, [blobs], [comms], [proofs]])
|
||||
// V1: type_byte || rlp([tx_fields, version, [blobs], [comms], [proofs]])
|
||||
func encodeForNetwork(storedRLP []byte) ([]byte, error) {
|
||||
elems, err := rlp.SplitListValues(storedRLP)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid blobTxForPool RLP: %w", err)
|
||||
}
|
||||
if len(elems) < 5 {
|
||||
return nil, fmt.Errorf("blobTxForPool has %d elements, need at least 5", len(elems))
|
||||
}
|
||||
|
||||
// 1. Extract tx byte and other tx fields
|
||||
txBytes, _, err := rlp.SplitString(elems[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid tx bytes: %w", err)
|
||||
}
|
||||
if len(txBytes) < 2 {
|
||||
return nil, errors.New("tx bytes too short")
|
||||
}
|
||||
typeByte := txBytes[0]
|
||||
txRLP := txBytes[1:]
|
||||
|
||||
// 2. Find the version of sidecar.
|
||||
version, _, err := rlp.SplitUint64(elems[1])
|
||||
if err != nil || version > 255 {
|
||||
return nil, fmt.Errorf("invalid version: %w", err)
|
||||
}
|
||||
versionByte := byte(version)
|
||||
// 3. Extract sidecar elements.
|
||||
commitmentsRLP := elems[2]
|
||||
proofsRLP := elems[3]
|
||||
blobsRLP := elems[4]
|
||||
|
||||
// 4. Reconstruct into the network format.
|
||||
var outer [][]byte
|
||||
if versionByte == types.BlobSidecarVersion0 {
|
||||
outer = [][]byte{txRLP, blobsRLP, commitmentsRLP, proofsRLP}
|
||||
} else {
|
||||
outer = [][]byte{txRLP, elems[1], blobsRLP, commitmentsRLP, proofsRLP}
|
||||
}
|
||||
body, err := rlp.MergeListValues(outer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Prepend type byte and wrap as an RLP string.
|
||||
inner := make([]byte, 1+len(body))
|
||||
inner[0] = typeByte
|
||||
copy(inner[1:], body)
|
||||
return rlp.EncodeToBytes(inner)
|
||||
}
|
||||
|
||||
// newBlobTxMeta retrieves the indexed metadata fields from a pooled blob
|
||||
// transaction and assembles a helper struct to track in memory.
|
||||
func newBlobTxMeta(id uint64, size uint64, storageSize uint32, ptx *blobTxForPool) *blobTxMeta {
|
||||
meta := &blobTxMeta{
|
||||
hash: tx.Hash(),
|
||||
vhashes: tx.BlobHashes(),
|
||||
version: tx.BlobTxSidecar().Version,
|
||||
hash: ptx.Tx.Hash(),
|
||||
vhashes: ptx.Tx.BlobHashes(),
|
||||
version: ptx.Version,
|
||||
id: id,
|
||||
storageSize: storageSize,
|
||||
size: size,
|
||||
nonce: tx.Nonce(),
|
||||
costCap: uint256.MustFromBig(tx.Cost()),
|
||||
execTipCap: uint256.MustFromBig(tx.GasTipCap()),
|
||||
execFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
|
||||
blobFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap()),
|
||||
execGas: tx.Gas(),
|
||||
blobGas: tx.BlobGas(),
|
||||
nonce: ptx.Tx.Nonce(),
|
||||
costCap: uint256.MustFromBig(ptx.Tx.Cost()),
|
||||
execTipCap: uint256.MustFromBig(ptx.Tx.GasTipCap()),
|
||||
execFeeCap: uint256.MustFromBig(ptx.Tx.GasFeeCap()),
|
||||
blobFeeCap: uint256.MustFromBig(ptx.Tx.BlobGasFeeCap()),
|
||||
execGas: ptx.Tx.Gas(),
|
||||
blobGas: ptx.Tx.BlobGas(),
|
||||
}
|
||||
meta.basefeeJumps = dynamicFeeJumps(meta.execFeeCap)
|
||||
meta.blobfeeJumps = dynamicBlobFeeJumps(meta.blobFeeCap)
|
||||
|
|
@ -460,10 +571,17 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
|
|||
return err
|
||||
}
|
||||
// Index all transactions on disk and delete anything unprocessable
|
||||
var fails []uint64
|
||||
var (
|
||||
toDelete []uint64
|
||||
convertTxs []uint64
|
||||
)
|
||||
index := func(id uint64, size uint32, blob []byte) {
|
||||
if p.parseTransaction(id, size, blob) != nil {
|
||||
fails = append(fails, id)
|
||||
err := p.parseTransaction(id, size, blob)
|
||||
if err != nil {
|
||||
toDelete = append(toDelete, id)
|
||||
}
|
||||
if errors.Is(err, errLegacyTx) {
|
||||
convertTxs = append(convertTxs, id)
|
||||
}
|
||||
}
|
||||
store, err := billy.Open(billy.Options{Path: queuedir, Repair: true}, slotter, index)
|
||||
|
|
@ -472,17 +590,58 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
|
|||
}
|
||||
p.store = store
|
||||
|
||||
if len(fails) > 0 {
|
||||
log.Warn("Dropping invalidated blob transactions", "ids", fails)
|
||||
dropInvalidMeter.Mark(int64(len(fails)))
|
||||
// Migrate legacy transactions (types.Transaction) to pooledBlobTx format.
|
||||
if len(convertTxs) > 0 {
|
||||
for _, id := range convertTxs {
|
||||
var tx types.Transaction
|
||||
data, err := p.store.Get(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
err = rlp.DecodeBytes(data, &tx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if tx.BlobTxSidecar() == nil {
|
||||
continue
|
||||
}
|
||||
ptx := newBlobTxForPool(&tx)
|
||||
blob, err := rlp.EncodeToBytes(ptx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
id, err := p.store.Put(blob)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
meta := newBlobTxMeta(id, ptx.TxSize(), p.store.Size(id), ptx)
|
||||
|
||||
for _, id := range fails {
|
||||
// If the newly inserted transaction fails to be tracked,
|
||||
// it should also be removed with those in `toDelete`
|
||||
sender, err := types.Sender(p.signer, ptx.Tx)
|
||||
if err != nil {
|
||||
toDelete = append(toDelete, id)
|
||||
continue
|
||||
}
|
||||
if err := p.trackTransaction(meta, sender); err != nil {
|
||||
toDelete = append(toDelete, id)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(toDelete) > 0 {
|
||||
log.Warn("Dropping invalidated blob transactions", "ids", toDelete)
|
||||
dropInvalidMeter.Mark(int64(len(toDelete)))
|
||||
|
||||
for _, id := range toDelete {
|
||||
if err := p.store.Delete(id); err != nil {
|
||||
p.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the indexed transactions by nonce and delete anything gapped, create
|
||||
// the eviction heap of anyone still standing
|
||||
for addr := range p.index {
|
||||
|
|
@ -558,36 +717,33 @@ func (p *BlobPool) Close() error {
|
|||
|
||||
// parseTransaction is a callback method on pool creation that gets called for
|
||||
// each transaction on disk to create the in-memory metadata index.
|
||||
// Announced state is not initialized here, it needs to be iniitalized seprately.
|
||||
// Return value `bool` is set to true when the entry has old Transaction type.
|
||||
func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
|
||||
tx := new(types.Transaction)
|
||||
if err := rlp.DecodeBytes(blob, tx); err != nil {
|
||||
// This path is impossible unless the disk data representation changes
|
||||
// across restarts. For that ever improbable case, recover gracefully
|
||||
// by ignoring this data entry.
|
||||
log.Error("Failed to decode blob pool entry", "id", id, "err", err)
|
||||
var ptx blobTxForPool
|
||||
if err := rlp.DecodeBytes(blob, &ptx); err != nil {
|
||||
kind, content, _, splitErr := rlp.Split(blob)
|
||||
// check whether it is legacy tx type
|
||||
if splitErr == nil && kind == rlp.String && len(content) > 1 && content[0] == 3 {
|
||||
return errLegacyTx
|
||||
}
|
||||
return err
|
||||
}
|
||||
if tx.BlobTxSidecar() == nil {
|
||||
log.Error("Missing sidecar in blob pool entry", "id", id, "hash", tx.Hash())
|
||||
return errors.New("missing blob sidecar")
|
||||
meta := newBlobTxMeta(id, ptx.TxSize(), size, &ptx)
|
||||
sender, err := types.Sender(p.signer, ptx.Tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.trackTransaction(meta, sender)
|
||||
}
|
||||
|
||||
meta := newBlobTxMeta(id, tx.Size(), size, tx)
|
||||
// trackTransaction registers a transaction's metadata in the pool's indices.
|
||||
func (p *BlobPool) trackTransaction(meta *blobTxMeta, sender common.Address) error {
|
||||
if p.lookup.exists(meta.hash) {
|
||||
// This path is only possible after a crash, where deleted items are not
|
||||
// removed via the normal shutdown-startup procedure and thus may get
|
||||
// partially resurrected.
|
||||
log.Error("Rejecting duplicate blob pool entry", "id", id, "hash", tx.Hash())
|
||||
return errors.New("duplicate blob entry")
|
||||
}
|
||||
sender, err := types.Sender(p.signer, tx)
|
||||
if err != nil {
|
||||
// This path is impossible unless the signature validity changes across
|
||||
// restarts. For that ever improbable case, recover gracefully by ignoring
|
||||
// this data entry.
|
||||
log.Error("Failed to recover blob tx sender", "id", id, "hash", tx.Hash(), "err", err)
|
||||
return err
|
||||
log.Error("Rejecting duplicate blob pool entry", "id", meta.id, "hash", meta.hash)
|
||||
return fmt.Errorf("duplicate blob entry %d, %s", meta.id, meta.hash)
|
||||
}
|
||||
if _, ok := p.index[sender]; !ok {
|
||||
if err := p.reserver.Hold(sender); err != nil {
|
||||
|
|
@ -863,17 +1019,17 @@ func (p *BlobPool) offload(addr common.Address, nonce uint64, id uint64, inclusi
|
|||
log.Error("Blobs missing for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
|
||||
return
|
||||
}
|
||||
var tx types.Transaction
|
||||
if err = rlp.DecodeBytes(data, &tx); err != nil {
|
||||
var ptx blobTxForPool
|
||||
if err := rlp.DecodeBytes(data, &ptx); err != nil {
|
||||
log.Error("Blobs corrupted for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
|
||||
return
|
||||
}
|
||||
block, ok := inclusions[tx.Hash()]
|
||||
block, ok := inclusions[ptx.Tx.Hash()]
|
||||
if !ok {
|
||||
log.Warn("Blob transaction swapped out by signer", "from", addr, "nonce", nonce, "id", id)
|
||||
return
|
||||
}
|
||||
if err := p.limbo.push(&tx, block); err != nil {
|
||||
if err := p.limbo.push(&ptx, block); err != nil {
|
||||
log.Warn("Failed to offload blob tx into limbo", "err", err)
|
||||
return
|
||||
}
|
||||
|
|
@ -1108,7 +1264,7 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
|
|||
func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
|
||||
// Retrieve the associated blob from the limbo. Without the blobs, we cannot
|
||||
// add the transaction back into the pool as it is not mineable.
|
||||
tx, err := p.limbo.pull(txhash)
|
||||
ptx, err := p.limbo.pull(txhash)
|
||||
if err != nil {
|
||||
log.Error("Blobs unavailable, dropping reorged tx", "err", err)
|
||||
return err
|
||||
|
|
@ -1124,30 +1280,29 @@ func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
|
|||
// could theoretically halt a Geth node for ~1.2s by reorging per block. However,
|
||||
// this attack is financially inefficient to execute.
|
||||
head := p.head.Load()
|
||||
if p.chain.Config().IsOsaka(head.Number, head.Time) && tx.BlobTxSidecar().Version == types.BlobSidecarVersion0 {
|
||||
if err := tx.BlobTxSidecar().ToV1(); err != nil {
|
||||
if p.chain.Config().IsOsaka(head.Number, head.Time) && ptx.Version == types.BlobSidecarVersion0 {
|
||||
sc := ptx.Sidecar()
|
||||
if err := sc.ToV1(); err != nil {
|
||||
log.Error("Failed to convert the legacy sidecar", "err", err)
|
||||
return err
|
||||
}
|
||||
log.Info("Legacy blob transaction is reorged", "hash", tx.Hash())
|
||||
ptx.ApplySidecar(sc)
|
||||
log.Info("Legacy blob transaction is reorged", "hash", ptx.Tx.Hash())
|
||||
}
|
||||
// Serialize the transaction back into the primary datastore.
|
||||
blob, err := rlp.EncodeToBytes(tx)
|
||||
blob, err := rlp.EncodeToBytes(ptx)
|
||||
if err != nil {
|
||||
log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err)
|
||||
log.Error("Failed to encode transaction for storage", "hash", ptx.Tx.Hash(), "err", err)
|
||||
return err
|
||||
}
|
||||
id, err := p.store.Put(blob)
|
||||
if err != nil {
|
||||
log.Error("Failed to write transaction into storage", "hash", tx.Hash(), "err", err)
|
||||
log.Error("Failed to write transaction into storage", "hash", ptx.Tx.Hash(), "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the indices and metrics
|
||||
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), tx)
|
||||
meta := newBlobTxMeta(id, ptx.TxSize(), p.store.Size(id), ptx)
|
||||
if _, ok := p.index[addr]; !ok {
|
||||
if err := p.reserver.Hold(addr); err != nil {
|
||||
log.Warn("Failed to reserve account for blob pool", "tx", tx.Hash(), "from", addr, "err", err)
|
||||
log.Warn("Failed to reserve account for blob pool", "tx", ptx.Tx.Hash(), "from", addr, "err", err)
|
||||
return err
|
||||
}
|
||||
p.index[addr] = []*blobTxMeta{meta}
|
||||
|
|
@ -1404,20 +1559,29 @@ func (p *BlobPool) Get(hash common.Hash) *types.Transaction {
|
|||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
item := new(types.Transaction)
|
||||
if err := rlp.DecodeBytes(data, item); err != nil {
|
||||
var ptx blobTxForPool
|
||||
if err := rlp.DecodeBytes(data, &ptx); err != nil {
|
||||
id, _ := p.lookup.storeidOfTx(hash)
|
||||
|
||||
log.Error("Blobs corrupted for traced transaction",
|
||||
"hash", hash, "id", id, "err", err)
|
||||
return nil
|
||||
}
|
||||
return item
|
||||
return ptx.ToTx()
|
||||
}
|
||||
|
||||
// GetRLP returns a RLP-encoded transaction if it is contained in the pool.
|
||||
// GetRLP returns a RLP-encoded transaction for network if it is contained in the pool.
|
||||
// It converts the pool's internal type to the RLP format used by the eth protocol:
|
||||
// e.g. type_byte || [..., version, [blobs], [comms], [proofs]]
|
||||
func (p *BlobPool) GetRLP(hash common.Hash) []byte {
|
||||
return p.getRLP(hash)
|
||||
data := p.getRLP(hash)
|
||||
rlp, err := encodeForNetwork(data)
|
||||
if err != nil {
|
||||
log.Error("Failed to encode pooled tx into the network type", "hash", hash, "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return rlp
|
||||
}
|
||||
|
||||
// GetMetadata returns the transaction type and transaction size with the
|
||||
|
|
@ -1486,18 +1650,14 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo
|
|||
}
|
||||
|
||||
// Decode the blob transaction
|
||||
tx := new(types.Transaction)
|
||||
if err := rlp.DecodeBytes(data, tx); err != nil {
|
||||
var ptx blobTxForPool
|
||||
if err := rlp.DecodeBytes(data, &ptx); err != nil {
|
||||
log.Error("Blobs corrupted for traced transaction", "id", txID, "err", err)
|
||||
continue
|
||||
}
|
||||
sidecar := tx.BlobTxSidecar()
|
||||
if sidecar == nil {
|
||||
log.Error("Blob tx without sidecar", "hash", tx.Hash(), "id", txID)
|
||||
continue
|
||||
}
|
||||
sidecar := ptx.Sidecar()
|
||||
// Traverse the blobs in the transaction
|
||||
for i, hash := range tx.BlobHashes() {
|
||||
for i, hash := range ptx.Tx.BlobHashes() {
|
||||
list, ok := indices[hash]
|
||||
if !ok {
|
||||
continue // non-interesting blob
|
||||
|
|
@ -1517,7 +1677,8 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo
|
|||
case types.BlobSidecarVersion1:
|
||||
cellProofs, err := sidecar.CellProofsAt(i)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
log.Error("Failed to get cell proofs", "id", txID, "err", err)
|
||||
continue
|
||||
}
|
||||
pf = cellProofs
|
||||
}
|
||||
|
|
@ -1596,9 +1757,10 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
|
|||
// Store the tx in memory, and revalidate later
|
||||
from, _ := types.Sender(p.signer, tx)
|
||||
allowance := p.gappedAllowance(from)
|
||||
if allowance >= 1 && len(p.gapped) < maxGapped {
|
||||
if allowance >= 1 && len(p.gappedSource) < maxGapped {
|
||||
p.gapped[from] = append(p.gapped[from], tx)
|
||||
p.gappedSource[tx.Hash()] = from
|
||||
gappedGauge.Update(int64(len(p.gappedSource)))
|
||||
log.Trace("added tx to gapped blob queue", "allowance", allowance, "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "qlen", len(p.gapped[from]))
|
||||
return nil
|
||||
} else {
|
||||
|
|
@ -1606,6 +1768,7 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
|
|||
// transactions by keeping the old and dropping this one.
|
||||
// Thus replacing a gapped transaction with another gapped transaction
|
||||
// is discouraged.
|
||||
addGappedFullMeter.Mark(1)
|
||||
log.Trace("no gapped blob queue allowance", "allowance", allowance, "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "qlen", len(p.gapped[from]))
|
||||
}
|
||||
case errors.Is(err, core.ErrInsufficientFunds):
|
||||
|
|
@ -1641,7 +1804,8 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
|
|||
}
|
||||
// Transaction permitted into the pool from a nonce and cost perspective,
|
||||
// insert it into the database and update the indices
|
||||
blob, err := rlp.EncodeToBytes(tx)
|
||||
ptx := newBlobTxForPool(tx)
|
||||
blob, err := rlp.EncodeToBytes(ptx)
|
||||
if err != nil {
|
||||
log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err)
|
||||
return err
|
||||
|
|
@ -1650,7 +1814,7 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), tx)
|
||||
meta := newBlobTxMeta(id, tx.Size(), p.store.Size(id), ptx)
|
||||
|
||||
var (
|
||||
next = p.state.GetNonce(from)
|
||||
|
|
@ -1791,6 +1955,7 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
|
|||
// We do not recurse here, but continue to loop instead.
|
||||
// We are under lock, so we can add the transaction directly.
|
||||
if err := p.addLocked(tx, false); err == nil {
|
||||
gappedPromotedMeter.Mark(1)
|
||||
log.Trace("Gapped blob transaction added to pool", "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "qlen", len(p.gapped[from]))
|
||||
} else {
|
||||
log.Trace("Gapped blob transaction not accepted", "hash", tx.Hash(), "from", from, "nonce", tx.Nonce(), "err", err)
|
||||
|
|
@ -1802,6 +1967,7 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
|
|||
} else {
|
||||
p.gapped[from] = gtxs
|
||||
}
|
||||
gappedGauge.Update(int64(len(p.gappedSource)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -2069,8 +2235,9 @@ func (p *BlobPool) evictGapped() {
|
|||
keep = append(keep, gtx)
|
||||
}
|
||||
}
|
||||
if len(keep) < len(txs) {
|
||||
log.Trace("Evicting old gapped blob transactions", "count", len(txs)-len(keep), "from", from)
|
||||
if evicted := len(txs) - len(keep); evicted > 0 {
|
||||
gappedEvictedMeter.Mark(int64(evicted))
|
||||
log.Trace("Evicting old gapped blob transactions", "count", evicted, "from", from)
|
||||
}
|
||||
if len(keep) == 0 {
|
||||
delete(p.gapped, from)
|
||||
|
|
@ -2078,6 +2245,7 @@ func (p *BlobPool) evictGapped() {
|
|||
p.gapped[from] = keep
|
||||
}
|
||||
}
|
||||
gappedGauge.Update(int64(len(p.gappedSource)))
|
||||
}
|
||||
|
||||
// isAnnouncable checks whether a transaction is announcable based on its
|
||||
|
|
|
|||
|
|
@ -235,6 +235,12 @@ func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64,
|
|||
return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx)
|
||||
}
|
||||
|
||||
// encodeForPool encodes a blob transaction in the blobTxForPool storage format.
|
||||
func encodeForPool(tx *types.Transaction) []byte {
|
||||
blob, _ := rlp.EncodeToBytes(newBlobTxForPool(tx))
|
||||
return blob
|
||||
}
|
||||
|
||||
// makeMultiBlobTx is a utility method to construct a ramdom blob tx with
|
||||
// certain number of blobs in its sidecar.
|
||||
func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, blobCount int, blobOffset int, key *ecdsa.PrivateKey, version byte) *types.Transaction {
|
||||
|
|
@ -530,7 +536,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
)
|
||||
for _, nonce := range []uint64{0, 1, 3, 4, 6, 7} { // first gap at #2, another at #5
|
||||
tx := makeTx(nonce, 1, 1, 1, gapper)
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if nonce < 2 {
|
||||
|
|
@ -547,7 +553,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
)
|
||||
for _, nonce := range []uint64{1, 2, 3} { // first gap at #0, all set dangling
|
||||
tx := makeTx(nonce, 1, 1, 1, dangler)
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
dangling[id] = struct{}{}
|
||||
|
|
@ -560,7 +566,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
)
|
||||
for _, nonce := range []uint64{0, 1, 2} { // account nonce at 3, all set filled
|
||||
tx := makeTx(nonce, 1, 1, 1, filler)
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
filled[id] = struct{}{}
|
||||
|
|
@ -573,7 +579,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
)
|
||||
for _, nonce := range []uint64{0, 1, 2, 3} { // account nonce at 2, half filled
|
||||
tx := makeTx(nonce, 1, 1, 1, overlapper)
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if nonce >= 2 {
|
||||
|
|
@ -595,7 +601,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
} else {
|
||||
tx = makeTx(uint64(i), 1, 1, 1, underpayer)
|
||||
}
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
underpaid[id] = struct{}{}
|
||||
|
|
@ -614,7 +620,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
} else {
|
||||
tx = makeTx(uint64(i), 1, 1, 1, outpricer)
|
||||
}
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if i < 2 {
|
||||
|
|
@ -636,7 +642,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
} else {
|
||||
tx = makeTx(nonce, 1, 1, 1, exceeder)
|
||||
}
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
exceeded[id] = struct{}{}
|
||||
|
|
@ -654,7 +660,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
} else {
|
||||
tx = makeTx(nonce, 1, 1, 1, overdrafter)
|
||||
}
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if nonce < 1 {
|
||||
|
|
@ -670,7 +676,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
overcapped = make(map[uint64]struct{})
|
||||
)
|
||||
for nonce := uint64(0); nonce < maxTxsPerAccount+3; nonce++ {
|
||||
blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, overcapper))
|
||||
blob := encodeForPool(makeTx(nonce, 1, 1, 1, overcapper))
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if nonce < maxTxsPerAccount {
|
||||
|
|
@ -686,7 +692,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
duplicated = make(map[uint64]struct{})
|
||||
)
|
||||
for _, nonce := range []uint64{0, 1, 2} {
|
||||
blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, duplicater))
|
||||
blob := encodeForPool(makeTx(nonce, 1, 1, 1, duplicater))
|
||||
|
||||
for i := 0; i < int(nonce)+1; i++ {
|
||||
id, _ := store.Put(blob)
|
||||
|
|
@ -705,7 +711,7 @@ func TestOpenDrops(t *testing.T) {
|
|||
)
|
||||
for _, nonce := range []uint64{0, 1, 2} {
|
||||
for i := 0; i < int(nonce)+1; i++ {
|
||||
blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, uint64(i)+1 /* unique hashes */, 1, repeater))
|
||||
blob := encodeForPool(makeTx(nonce, 1, uint64(i)+1 /* unique hashes */, 1, repeater))
|
||||
|
||||
id, _ := store.Put(blob)
|
||||
if i == 0 {
|
||||
|
|
@ -842,7 +848,7 @@ func TestOpenIndex(t *testing.T) {
|
|||
)
|
||||
for _, i := range []int{5, 3, 4, 2, 0, 1} { // Randomize the tx insertion order to force sorting on load
|
||||
tx := makeTx(uint64(i), txExecTipCaps[i], txExecFeeCaps[i], txBlobFeeCaps[i], key)
|
||||
blob, _ := rlp.EncodeToBytes(tx)
|
||||
blob := encodeForPool(tx)
|
||||
store.Put(blob)
|
||||
}
|
||||
store.Close()
|
||||
|
|
@ -934,9 +940,9 @@ func TestOpenHeap(t *testing.T) {
|
|||
tx2 = makeTx(0, 1, 800, 70, key2)
|
||||
tx3 = makeTx(0, 1, 1500, 110, key3)
|
||||
|
||||
blob1, _ = rlp.EncodeToBytes(tx1)
|
||||
blob2, _ = rlp.EncodeToBytes(tx2)
|
||||
blob3, _ = rlp.EncodeToBytes(tx3)
|
||||
blob1 = encodeForPool(tx1)
|
||||
blob2 = encodeForPool(tx2)
|
||||
blob3 = encodeForPool(tx3)
|
||||
|
||||
heapOrder = []common.Address{addr2, addr1, addr3}
|
||||
heapIndex = map[common.Address]int{addr2: 0, addr1: 1, addr3: 2}
|
||||
|
|
@ -1009,9 +1015,9 @@ func TestOpenCap(t *testing.T) {
|
|||
tx2 = makeTx(0, 1, 800, 70, key2)
|
||||
tx3 = makeTx(0, 1, 1500, 110, key3)
|
||||
|
||||
blob1, _ = rlp.EncodeToBytes(tx1)
|
||||
blob2, _ = rlp.EncodeToBytes(tx2)
|
||||
blob3, _ = rlp.EncodeToBytes(tx3)
|
||||
blob1 = encodeForPool(tx1)
|
||||
blob2 = encodeForPool(tx2)
|
||||
blob3 = encodeForPool(tx3)
|
||||
|
||||
keep = []common.Address{addr1, addr3}
|
||||
drop = []common.Address{addr2}
|
||||
|
|
@ -1098,8 +1104,8 @@ func TestChangingSlotterSize(t *testing.T) {
|
|||
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 0, key2, types.BlobSidecarVersion0)
|
||||
tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3, types.BlobSidecarVersion0)
|
||||
|
||||
blob1, _ = rlp.EncodeToBytes(tx1)
|
||||
blob2, _ = rlp.EncodeToBytes(tx2)
|
||||
blob1 = encodeForPool(tx1)
|
||||
blob2 = encodeForPool(tx2)
|
||||
)
|
||||
|
||||
// Write the two safely sized txs to store. note: although the store is
|
||||
|
|
@ -1201,8 +1207,8 @@ func TestBillyMigration(t *testing.T) {
|
|||
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 0, key2, types.BlobSidecarVersion0)
|
||||
tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3, types.BlobSidecarVersion0)
|
||||
|
||||
blob1, _ = rlp.EncodeToBytes(tx1)
|
||||
blob2, _ = rlp.EncodeToBytes(tx2)
|
||||
blob1 = encodeForPool(tx1)
|
||||
blob2 = encodeForPool(tx2)
|
||||
)
|
||||
|
||||
// Write the two safely sized txs to store. note: although the store is
|
||||
|
|
@ -1281,6 +1287,85 @@ func TestBillyMigration(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestLegacyTxConversion verifies that on Init, transactions stored in the
|
||||
// legacy *types.Transaction RLP format are detected and migrated into the new
|
||||
// blobTxForPool storage format, and that they remain retrievable via the pool
|
||||
// API after the conversion.
|
||||
func TestLegacyTxConversion(t *testing.T) {
|
||||
storage := t.TempDir()
|
||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||
os.MkdirAll(filepath.Join(storage, limboedTransactionStore), 0700)
|
||||
|
||||
// Initialize the pending store with two blob transactions encoded in the
|
||||
// legacy format.
|
||||
queuedir := filepath.Join(storage, pendingTransactionStore)
|
||||
store, err := billy.Open(billy.Options{Path: queuedir}, newSlotter(testMaxBlobsPerBlock), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open billy: %v", err)
|
||||
}
|
||||
|
||||
key1, _ := crypto.GenerateKey()
|
||||
key2, _ := crypto.GenerateKey()
|
||||
addr1 := crypto.PubkeyToAddress(key1.PublicKey)
|
||||
addr2 := crypto.PubkeyToAddress(key2.PublicKey)
|
||||
|
||||
tx1 := makeMultiBlobTx(0, 1, 1000, 100, 2, 0, key1, types.BlobSidecarVersion0)
|
||||
tx2 := makeMultiBlobTx(0, 1, 1000, 100, 2, 2, key2, types.BlobSidecarVersion0)
|
||||
|
||||
for _, tx := range []*types.Transaction{tx1, tx2} {
|
||||
legacy, err := rlp.EncodeToBytes(tx)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to legacy-encode tx: %v", err)
|
||||
}
|
||||
if _, err := store.Put(legacy); err != nil {
|
||||
t.Fatalf("failed to put legacy blob: %v", err)
|
||||
}
|
||||
}
|
||||
store.Close()
|
||||
|
||||
// Init should migrate the legacy entries into the new storage format.
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||
statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
|
||||
statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
|
||||
statedb.Commit(0, true, false)
|
||||
|
||||
chain := &testBlockChain{
|
||||
config: params.MainnetChainConfig,
|
||||
basefee: uint256.NewInt(params.InitialBaseFee),
|
||||
blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice),
|
||||
statedb: statedb,
|
||||
}
|
||||
pool := New(Config{Datadir: storage}, chain, nil)
|
||||
if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil {
|
||||
t.Fatalf("failed to create blob pool: %v", err)
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
// Both transactions should be retrievable.
|
||||
for _, want := range []*types.Transaction{tx1, tx2} {
|
||||
got := pool.Get(want.Hash())
|
||||
if got == nil {
|
||||
t.Fatalf("migrated tx %s not found in pool", want.Hash())
|
||||
}
|
||||
if got.BlobTxSidecar() == nil {
|
||||
t.Fatalf("migrated tx %s lost its sidecar", want.Hash())
|
||||
}
|
||||
if got.Hash() != want.Hash() {
|
||||
t.Fatalf("migrated tx hash mismatch: have %s, want %s", got.Hash(), want.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy formats should not exist on pool.store
|
||||
pool.store.Iterate(func(id uint64, size uint32, blob []byte) {
|
||||
var ptx blobTxForPool
|
||||
if err := rlp.DecodeBytes(blob, &ptx); err != nil {
|
||||
t.Errorf("entry %d not in new blobTxForPool format: %v", id, err)
|
||||
}
|
||||
})
|
||||
|
||||
verifyPoolInternals(t, pool)
|
||||
}
|
||||
|
||||
// TestBlobCountLimit tests the blobpool enforced limits on the max blob count.
|
||||
func TestBlobCountLimit(t *testing.T) {
|
||||
var (
|
||||
|
|
@ -1746,7 +1831,7 @@ func TestAdd(t *testing.T) {
|
|||
// Sign the seed transactions and store them in the data store
|
||||
for _, tx := range seed.txs {
|
||||
signed := types.MustSignNewTx(keys[acc], types.LatestSigner(params.MainnetChainConfig), tx)
|
||||
blob, _ := rlp.EncodeToBytes(signed)
|
||||
blob := encodeForPool(signed)
|
||||
store.Put(blob)
|
||||
}
|
||||
}
|
||||
|
|
@ -1853,9 +1938,9 @@ func TestGetBlobs(t *testing.T) {
|
|||
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 6, key2, types.BlobSidecarVersion1) // [6, 12)
|
||||
tx3 = makeMultiBlobTx(0, 1, 800, 110, 6, 12, key3, types.BlobSidecarVersion0) // [12, 18)
|
||||
|
||||
blob1, _ = rlp.EncodeToBytes(tx1)
|
||||
blob2, _ = rlp.EncodeToBytes(tx2)
|
||||
blob3, _ = rlp.EncodeToBytes(tx3)
|
||||
blob1 = encodeForPool(tx1)
|
||||
blob2 = encodeForPool(tx2)
|
||||
blob3 = encodeForPool(tx3)
|
||||
)
|
||||
|
||||
// Write the two safely sized txs to store. note: although the store is
|
||||
|
|
@ -2055,6 +2140,32 @@ func TestGetBlobs(t *testing.T) {
|
|||
pool.Close()
|
||||
}
|
||||
|
||||
// TestEncodeForNetwork verifies that encodeForNetwork produces output identical
|
||||
// to rlp.EncodeToBytes on the original transaction, for both V0 and V1 sidecars.
|
||||
func TestEncodeForNetwork(t *testing.T) {
|
||||
t.Run("v0", func(t *testing.T) { testEncodeForNetwork(t, types.BlobSidecarVersion0) })
|
||||
t.Run("v1", func(t *testing.T) { testEncodeForNetwork(t, types.BlobSidecarVersion1) })
|
||||
}
|
||||
|
||||
func testEncodeForNetwork(t *testing.T, version byte) {
|
||||
key, _ := crypto.GenerateKey()
|
||||
tx := makeMultiBlobTx(0, 1, 1, 1, 1, 0, key, version)
|
||||
|
||||
wantRLP, err := rlp.EncodeToBytes(tx)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to encode tx: %v", err)
|
||||
}
|
||||
storedRLP := encodeForPool(tx)
|
||||
|
||||
gotRLP, err := encodeForNetwork(storedRLP)
|
||||
if err != nil {
|
||||
t.Fatalf("encodeForNetwork failed: %v", err)
|
||||
}
|
||||
if !bytes.Equal(gotRLP, wantRLP) {
|
||||
t.Fatalf("network encoding mismatch (version %d): got %d bytes, want %d bytes", version, len(gotRLP), len(wantRLP))
|
||||
}
|
||||
}
|
||||
|
||||
// fakeBilly is a billy.Database implementation which just drops data on the floor.
|
||||
type fakeBilly struct {
|
||||
billy.Database
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import (
|
|||
type limboBlob struct {
|
||||
TxHash common.Hash // Owner transaction's hash to support resurrecting reorged txs
|
||||
Block uint64 // Block in which the blob transaction was included
|
||||
Tx *types.Transaction
|
||||
Ptx *blobTxForPool
|
||||
}
|
||||
|
||||
// limbo is a light, indexed database to temporarily store recently included
|
||||
|
|
@ -146,15 +146,14 @@ func (l *limbo) finalize(final *types.Header) {
|
|||
|
||||
// push stores a new blob transaction into the limbo, waiting until finality for
|
||||
// it to be automatically evicted.
|
||||
func (l *limbo) push(tx *types.Transaction, block uint64) error {
|
||||
// If the blobs are already tracked by the limbo, consider it a programming
|
||||
// error. There's not much to do against it, but be loud.
|
||||
if _, ok := l.index[tx.Hash()]; ok {
|
||||
log.Error("Limbo cannot push already tracked blobs", "tx", tx.Hash())
|
||||
func (l *limbo) push(ptx *blobTxForPool, block uint64) error {
|
||||
hash := ptx.Tx.Hash()
|
||||
if _, ok := l.index[hash]; ok {
|
||||
log.Error("Limbo cannot push already tracked blobs", "tx", hash)
|
||||
return errors.New("already tracked blob transaction")
|
||||
}
|
||||
if err := l.setAndIndex(tx, block); err != nil {
|
||||
log.Error("Failed to set and index limboed blobs", "tx", tx.Hash(), "err", err)
|
||||
if err := l.setAndIndex(ptx, block); err != nil {
|
||||
log.Error("Failed to set and index limboed blobs", "tx", hash, "err", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
@ -163,7 +162,7 @@ func (l *limbo) push(tx *types.Transaction, block uint64) error {
|
|||
// pull retrieves a previously pushed set of blobs back from the limbo, removing
|
||||
// it at the same time. This method should be used when a previously included blob
|
||||
// transaction gets reorged out.
|
||||
func (l *limbo) pull(tx common.Hash) (*types.Transaction, error) {
|
||||
func (l *limbo) pull(tx common.Hash) (*blobTxForPool, error) {
|
||||
// If the blobs are not tracked by the limbo, there's not much to do. This
|
||||
// can happen for example if a blob transaction is mined without pushing it
|
||||
// into the network first.
|
||||
|
|
@ -177,7 +176,7 @@ func (l *limbo) pull(tx common.Hash) (*types.Transaction, error) {
|
|||
log.Error("Failed to get and drop limboed blobs", "tx", tx, "id", id, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
return item.Tx, nil
|
||||
return item.Ptx, nil
|
||||
}
|
||||
|
||||
// update changes the block number under which a blob transaction is tracked. This
|
||||
|
|
@ -209,7 +208,7 @@ func (l *limbo) update(txhash common.Hash, block uint64) {
|
|||
log.Error("Failed to get and drop limboed blobs", "tx", txhash, "id", id, "err", err)
|
||||
return
|
||||
}
|
||||
if err := l.setAndIndex(item.Tx, block); err != nil {
|
||||
if err := l.setAndIndex(item.Ptx, block); err != nil {
|
||||
log.Error("Failed to set and index limboed blobs", "tx", txhash, "err", err)
|
||||
return
|
||||
}
|
||||
|
|
@ -240,12 +239,12 @@ func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) {
|
|||
|
||||
// setAndIndex assembles a limbo blob database entry and stores it, also updating
|
||||
// the in-memory indices.
|
||||
func (l *limbo) setAndIndex(tx *types.Transaction, block uint64) error {
|
||||
txhash := tx.Hash()
|
||||
func (l *limbo) setAndIndex(ptx *blobTxForPool, block uint64) error {
|
||||
txhash := ptx.Tx.Hash()
|
||||
item := &limboBlob{
|
||||
TxHash: txhash,
|
||||
Block: block,
|
||||
Tx: tx,
|
||||
Ptx: ptx,
|
||||
}
|
||||
data, err := rlp.EncodeToBytes(item)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -97,9 +97,15 @@ var (
|
|||
addUnderpricedMeter = metrics.NewRegisteredMeter("blobpool/add/underpriced", nil) // Gas tip too low, neutral
|
||||
addStaleMeter = metrics.NewRegisteredMeter("blobpool/add/stale", nil) // Nonce already filled, reject, bad-ish
|
||||
addGappedMeter = metrics.NewRegisteredMeter("blobpool/add/gapped", nil) // Nonce gapped, reject, bad-ish
|
||||
addGappedFullMeter = metrics.NewRegisteredMeter("blobpool/add/gappedfull", nil) // Gapped queue full, reject, neutral
|
||||
addOverdraftedMeter = metrics.NewRegisteredMeter("blobpool/add/overdrafted", nil) // Balance exceeded, reject, neutral
|
||||
addOvercappedMeter = metrics.NewRegisteredMeter("blobpool/add/overcapped", nil) // Per-account cap exceeded, reject, neutral
|
||||
addNoreplaceMeter = metrics.NewRegisteredMeter("blobpool/add/noreplace", nil) // Replacement fees or tips too low, neutral
|
||||
addNonExclusiveMeter = metrics.NewRegisteredMeter("blobpool/add/nonexclusive", nil) // Plain transaction from same account exists, reject, neutral
|
||||
addValidMeter = metrics.NewRegisteredMeter("blobpool/add/valid", nil) // Valid transaction, add, neutral
|
||||
|
||||
// Gapped queue metrics for observability
|
||||
gappedGauge = metrics.NewRegisteredGauge("blobpool/gapped/count", nil) // Current gapped queue size
|
||||
gappedPromotedMeter = metrics.NewRegisteredMeter("blobpool/gapped/promoted", nil) // Gapped txs successfully promoted to pool
|
||||
gappedEvictedMeter = metrics.NewRegisteredMeter("blobpool/gapped/evicted", nil) // Gapped txs evicted due to timeout/stale
|
||||
)
|
||||
|
|
|
|||
|
|
@ -467,8 +467,8 @@ func (pool *LegacyPool) stats() (int, int) {
|
|||
// Content retrieves the data content of the transaction pool, returning all the
|
||||
// pending as well as queued transactions, grouped by account and sorted by nonce.
|
||||
func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
|
||||
pool.mu.Lock()
|
||||
defer pool.mu.Unlock()
|
||||
pool.mu.RLock()
|
||||
defer pool.mu.RUnlock()
|
||||
|
||||
pending := make(map[common.Address][]*types.Transaction, len(pool.pending))
|
||||
for addr, list := range pool.pending {
|
||||
|
|
@ -503,8 +503,8 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) (map[common.Address
|
|||
if filter.BlobTxs {
|
||||
return nil, 0
|
||||
}
|
||||
pool.mu.Lock()
|
||||
defer pool.mu.Unlock()
|
||||
pool.mu.RLock()
|
||||
defer pool.mu.RUnlock()
|
||||
|
||||
var count int
|
||||
pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package locals
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -151,7 +152,7 @@ func (tracker *TxTracker) recheck(journalCheck bool) []*types.Transaction {
|
|||
for _, list := range rejournal {
|
||||
// cmp(a, b) should return a negative number when a < b,
|
||||
slices.SortFunc(list, func(a, b *types.Transaction) int {
|
||||
return int(a.Nonce() - b.Nonce())
|
||||
return cmp.Compare(a.Nonce(), b.Nonce())
|
||||
})
|
||||
}
|
||||
// Rejournal the tracker while holding the lock. No new transactions will
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
}
|
||||
// Ensure the transaction has more gas than the bare minimum needed to cover
|
||||
// the transaction metadata
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai)
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -134,7 +134,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
}
|
||||
// Ensure the transaction can cover floor data gas.
|
||||
if rules.IsPrague {
|
||||
floorDataGas, err := core.FloorDataGas(rules, tx.Data())
|
||||
floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ func Sign(hash []byte, prv *ecdsa.PrivateKey) ([]byte, error) {
|
|||
// The public key should be in compressed (33 bytes) or uncompressed (65 bytes) format.
|
||||
// The signature should have the 64 byte [R || S] format.
|
||||
func VerifySignature(pubkey, hash, signature []byte) bool {
|
||||
if len(signature) != 64 {
|
||||
if len(signature) != 64 || len(hash) != DigestLength {
|
||||
return false
|
||||
}
|
||||
var r, s secp256k1.ModNScalar
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/internal/shutdowncheck"
|
||||
"github.com/ethereum/go-ethereum/internal/version"
|
||||
|
|
@ -105,7 +104,6 @@ type Ethereum struct {
|
|||
// DB interfaces
|
||||
chainDb ethdb.Database // Block chain database
|
||||
|
||||
eventMux *event.TypeMux
|
||||
engine consensus.Engine
|
||||
accountManager *accounts.Manager
|
||||
|
||||
|
|
@ -194,7 +192,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
eth := &Ethereum{
|
||||
config: config,
|
||||
chainDb: chainDb,
|
||||
eventMux: stack.EventMux(),
|
||||
accountManager: stack.AccountManager(),
|
||||
engine: engine,
|
||||
networkID: networkID,
|
||||
|
|
@ -237,6 +234,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
StateHistory: config.StateHistory,
|
||||
TrienodeHistory: config.TrienodeHistory,
|
||||
NodeFullValueCheckpoint: config.NodeFullValueCheckpoint,
|
||||
BinTrieGroupDepth: config.BinTrieGroupDepth,
|
||||
StateScheme: scheme,
|
||||
HistoryPolicy: histPolicy,
|
||||
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
|
||||
|
|
@ -343,7 +341,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
Network: networkID,
|
||||
Sync: config.SyncMode,
|
||||
BloomCache: uint64(cacheLimit),
|
||||
EventMux: eth.eventMux,
|
||||
RequiredBlocks: config.RequiredBlocks,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -404,7 +401,7 @@ func (s *Ethereum) APIs() []rpc.API {
|
|||
Service: NewMinerAPI(s),
|
||||
}, {
|
||||
Namespace: "eth",
|
||||
Service: downloader.NewDownloaderAPI(s.handler.downloader, s.blockchain, s.eventMux),
|
||||
Service: downloader.NewDownloaderAPI(s.handler.downloader, s.blockchain),
|
||||
}, {
|
||||
Namespace: "admin",
|
||||
Service: NewAdminAPI(s),
|
||||
|
|
@ -599,7 +596,6 @@ func (s *Ethereum) Stop() error {
|
|||
s.shutdownTracker.Stop()
|
||||
|
||||
s.chainDb.Close()
|
||||
s.eventMux.Stop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,9 @@ const (
|
|||
// beaconUpdateWarnFrequency is the frequency at which to warn the user that
|
||||
// the beacon client is offline.
|
||||
beaconUpdateWarnFrequency = 5 * time.Minute
|
||||
|
||||
// maxReorgDepth is the maximum reorg depth accepted via forkchoiceUpdated.
|
||||
maxReorgDepth = 32
|
||||
)
|
||||
|
||||
type ConsensusAPI struct {
|
||||
|
|
@ -237,6 +240,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV4(ctx context.Context, update engine.
|
|||
func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (result engine.ForkChoiceResponse, err error) {
|
||||
ctx, _, spanEnd := telemetry.StartSpan(ctx, "engine.forkchoiceUpdated")
|
||||
defer spanEnd(&err)
|
||||
|
||||
api.forkchoiceLock.Lock()
|
||||
defer api.forkchoiceLock.Unlock()
|
||||
|
||||
|
|
@ -321,10 +325,23 @@ func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.Fo
|
|||
// generating the payload. It's a special corner case that a few slots are
|
||||
// missing and we are requested to generate the payload in slot.
|
||||
} else {
|
||||
// If the head block is already in our canonical chain, the beacon client is
|
||||
// probably resyncing. Ignore the update.
|
||||
log.Info("Ignoring beacon update to old head", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().Number)
|
||||
return valid(nil), nil
|
||||
if finalized := api.eth.BlockChain().CurrentFinalBlock(); finalized != nil && block.NumberU64() <= finalized.Number.Uint64() {
|
||||
log.Info("Skipping beacon update to finalized ancestor", "number", block.NumberU64(), "hash", update.HeadBlockHash)
|
||||
return valid(nil), nil
|
||||
}
|
||||
depth := api.eth.BlockChain().CurrentBlock().Number.Uint64() - block.NumberU64()
|
||||
if depth >= maxReorgDepth {
|
||||
log.Warn("Refusing too deep reorg", "depth", depth, "head", update.HeadBlockHash)
|
||||
return engine.STATUS_INVALID, engine.TooDeepReorg.With(fmt.Errorf("reorg depth %d exceeds limit %d", depth, maxReorgDepth))
|
||||
}
|
||||
if !api.eth.Synced() {
|
||||
log.Info("Ignoring beacon update to old head while syncing", "number", block.NumberU64(), "hash", update.HeadBlockHash)
|
||||
return valid(nil), nil
|
||||
}
|
||||
if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
|
||||
log.Error("Error setting canonical", "number", block.NumberU64(), "hash", update.HeadBlockHash, "error", err)
|
||||
return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err
|
||||
}
|
||||
}
|
||||
api.eth.SetSynced()
|
||||
|
||||
|
|
@ -629,6 +646,7 @@ func (api *ConsensusAPI) getBlobs(hashes []common.Hash, v2 bool) ([]*engine.Blob
|
|||
return nil, engine.InvalidParams.With(err)
|
||||
}
|
||||
// Validate the blobs from the pool and assemble the response
|
||||
filled := 0
|
||||
res := make([]*engine.BlobAndProofV2, len(hashes))
|
||||
for i := range blobs {
|
||||
// The blob has been evicted since the last AvailableBlobs call.
|
||||
|
|
@ -649,10 +667,11 @@ func (api *ConsensusAPI) getBlobs(hashes []common.Hash, v2 bool) ([]*engine.Blob
|
|||
Blob: blobs[i][:],
|
||||
CellProofs: cellProofs,
|
||||
}
|
||||
filled++
|
||||
}
|
||||
if len(res) == len(hashes) {
|
||||
if filled == len(hashes) {
|
||||
getBlobsRequestCompleteHit.Inc(1)
|
||||
} else if len(res) > 0 {
|
||||
} else if filled > 0 {
|
||||
getBlobsRequestPartialHit.Inc(1)
|
||||
} else {
|
||||
getBlobsRequestMiss.Inc(1)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
|
|
@ -33,20 +32,18 @@ import (
|
|||
type DownloaderAPI struct {
|
||||
d *Downloader
|
||||
chain *core.BlockChain
|
||||
mux *event.TypeMux
|
||||
installSyncSubscription chan chan interface{}
|
||||
uninstallSyncSubscription chan *uninstallSyncSubscriptionRequest
|
||||
}
|
||||
|
||||
// NewDownloaderAPI creates a new DownloaderAPI. The API has an internal event loop that
|
||||
// listens for events from the downloader through the global event mux. In case it receives one of
|
||||
// listens for events from the downloader through the event feed. In case it receives one of
|
||||
// these events it broadcasts it to all syncing subscriptions that are installed through the
|
||||
// installSyncSubscription channel.
|
||||
func NewDownloaderAPI(d *Downloader, chain *core.BlockChain, m *event.TypeMux) *DownloaderAPI {
|
||||
func NewDownloaderAPI(d *Downloader, chain *core.BlockChain) *DownloaderAPI {
|
||||
api := &DownloaderAPI{
|
||||
d: d,
|
||||
chain: chain,
|
||||
mux: m,
|
||||
installSyncSubscription: make(chan chan interface{}),
|
||||
uninstallSyncSubscription: make(chan *uninstallSyncSubscriptionRequest),
|
||||
}
|
||||
|
|
@ -66,7 +63,8 @@ func NewDownloaderAPI(d *Downloader, chain *core.BlockChain, m *event.TypeMux) *
|
|||
// receive is {false}.
|
||||
func (api *DownloaderAPI) eventLoop() {
|
||||
var (
|
||||
sub = api.mux.Subscribe(StartEvent{})
|
||||
events = make(chan SyncEvent, 16)
|
||||
sub = api.d.SubscribeSyncEvents(events)
|
||||
syncSubscriptions = make(map[chan interface{}]struct{})
|
||||
checkInterval = time.Second * 60
|
||||
checkTimer = time.NewTimer(checkInterval)
|
||||
|
|
@ -90,6 +88,7 @@ func (api *DownloaderAPI) eventLoop() {
|
|||
}
|
||||
)
|
||||
defer checkTimer.Stop()
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
for {
|
||||
select {
|
||||
|
|
@ -101,14 +100,13 @@ func (api *DownloaderAPI) eventLoop() {
|
|||
case u := <-api.uninstallSyncSubscription:
|
||||
delete(syncSubscriptions, u.c)
|
||||
close(u.uninstalled)
|
||||
case event := <-sub.Chan():
|
||||
if event == nil {
|
||||
return
|
||||
}
|
||||
switch event.Data.(type) {
|
||||
case StartEvent:
|
||||
case ev := <-events:
|
||||
if ev.Type == SyncStarted {
|
||||
started = true
|
||||
}
|
||||
case <-sub.Err():
|
||||
// The downloader is terminated or other internal error occurs
|
||||
return
|
||||
case <-checkTimer.C:
|
||||
if !started {
|
||||
checkTimer.Reset(checkInterval)
|
||||
|
|
|
|||
|
|
@ -97,9 +97,12 @@ type headerTask struct {
|
|||
}
|
||||
|
||||
type Downloader struct {
|
||||
mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
|
||||
moder *syncModer // Sync mode management, deliver the appropriate sync mode choice for each cycle
|
||||
mux *event.TypeMux // Event multiplexer to announce sync operation events
|
||||
mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
|
||||
moder *syncModer // Sync mode management, deliver the appropriate sync mode choice for each cycle
|
||||
|
||||
// Event feed for downloader events
|
||||
feed event.FeedOf[SyncEvent]
|
||||
scope event.SubscriptionScope
|
||||
|
||||
queue *queue // Scheduler for selecting the hashes to download
|
||||
peers *peerSet // Set of active peers from which download can proceed
|
||||
|
|
@ -229,12 +232,11 @@ type BlockChain interface {
|
|||
}
|
||||
|
||||
// New creates a new downloader to fetch hashes and blocks from remote peers.
|
||||
func New(stateDb ethdb.Database, mode ethconfig.SyncMode, mux *event.TypeMux, chain BlockChain, dropPeer peerDropFn, success func()) *Downloader {
|
||||
func New(stateDb ethdb.Database, mode ethconfig.SyncMode, chain BlockChain, dropPeer peerDropFn, success func()) *Downloader {
|
||||
cutoffNumber, cutoffHash := chain.HistoryPruningCutoff()
|
||||
dl := &Downloader{
|
||||
stateDB: stateDb,
|
||||
moder: newSyncModer(mode, chain, stateDb),
|
||||
mux: mux,
|
||||
queue: newQueue(blockCacheMaxItems, blockCacheInitialItems),
|
||||
peers: newPeerSet(),
|
||||
blockchain: chain,
|
||||
|
|
@ -427,20 +429,25 @@ func (d *Downloader) ConfigSyncMode() SyncMode {
|
|||
return d.moder.get(false)
|
||||
}
|
||||
|
||||
// SubscribeSyncEvents creates a subscription for downloader sync events
|
||||
func (d *Downloader) SubscribeSyncEvents(ch chan<- SyncEvent) event.Subscription {
|
||||
return d.scope.Track(d.feed.Subscribe(ch))
|
||||
}
|
||||
|
||||
// syncToHead starts a block synchronization based on the hash chain from
|
||||
// the specified head hash.
|
||||
func (d *Downloader) syncToHead() (err error) {
|
||||
d.mux.Post(StartEvent{})
|
||||
mode := d.getMode()
|
||||
d.feed.Send(SyncEvent{Type: SyncStarted, Mode: mode})
|
||||
defer func() {
|
||||
// reset on error
|
||||
if err != nil {
|
||||
d.mux.Post(FailedEvent{err})
|
||||
d.feed.Send(SyncEvent{Type: SyncFailed, Mode: mode, Err: err})
|
||||
} else {
|
||||
latest := d.blockchain.CurrentHeader()
|
||||
d.mux.Post(DoneEvent{latest})
|
||||
d.feed.Send(SyncEvent{Type: SyncCompleted, Mode: mode, Latest: latest})
|
||||
}
|
||||
}()
|
||||
mode := d.getMode()
|
||||
|
||||
log.Debug("Backfilling with the network", "mode", mode)
|
||||
defer func(start time.Time) {
|
||||
|
|
@ -662,6 +669,9 @@ func (d *Downloader) Cancel() {
|
|||
// Terminate interrupts the downloader, canceling all pending operations.
|
||||
// The downloader cannot be reused after calling Terminate.
|
||||
func (d *Downloader) Terminate() {
|
||||
// Unsubscribe all subscriptions registered from downloader
|
||||
d.scope.Close()
|
||||
|
||||
// Close the termination channel (make sure double close is allowed)
|
||||
d.quitLock.Lock()
|
||||
select {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
|
|
@ -75,7 +74,7 @@ func newTesterWithNotification(t *testing.T, mode ethconfig.SyncMode, success fu
|
|||
chain: chain,
|
||||
peers: make(map[string]*downloadTesterPeer),
|
||||
}
|
||||
tester.downloader = New(db, mode, new(event.TypeMux), tester.chain, tester.dropPeer, success)
|
||||
tester.downloader = New(db, mode, tester.chain, tester.dropPeer, success)
|
||||
return tester
|
||||
}
|
||||
|
||||
|
|
@ -96,6 +95,7 @@ func (dl *downloadTester) newPeer(id string, version uint, blocks []*types.Block
|
|||
id: id,
|
||||
chain: newTestBlockchain(blocks),
|
||||
withholdBodies: make(map[common.Hash]struct{}),
|
||||
dropped: make(chan error, 1),
|
||||
}
|
||||
dl.peers[id] = peer
|
||||
|
||||
|
|
@ -121,8 +121,11 @@ func (dl *downloadTester) dropPeer(id string) {
|
|||
type downloadTesterPeer struct {
|
||||
dl *downloadTester
|
||||
withholdBodies map[common.Hash]struct{}
|
||||
corruptBodies bool // if set, the peer serves incorrect blocks
|
||||
id string
|
||||
chain *core.BlockChain
|
||||
|
||||
dropped chan error // signaled when res.Done receives an error
|
||||
}
|
||||
|
||||
func unmarshalRlpHeaders(rlpdata []rlp.RawValue) []*types.Header {
|
||||
|
|
@ -236,6 +239,11 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
|
|||
txsHashes[i] = hash
|
||||
uncleHashes[i] = types.CalcUncleHash(body.Uncles)
|
||||
}
|
||||
if dlp.corruptBodies {
|
||||
for i := range txsHashes {
|
||||
txsHashes[i] = common.Hash{0xff}
|
||||
}
|
||||
}
|
||||
req := ð.Request{
|
||||
Peer: dlp.id,
|
||||
}
|
||||
|
|
@ -248,10 +256,16 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
|
|||
WithdrawalRoots: withdrawalHashes,
|
||||
},
|
||||
Time: 1,
|
||||
Done: make(chan error, 1), // Ignore the returned status
|
||||
Done: make(chan error),
|
||||
}
|
||||
go func() {
|
||||
sink <- res
|
||||
if err := <-res.Done; err != nil {
|
||||
select {
|
||||
case dlp.dropped <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
return req, nil
|
||||
}
|
||||
|
|
@ -704,3 +718,21 @@ func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
|||
t.Fatalf("Failed to sync chain in three seconds")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidBodyPeerDrop(t *testing.T) {
|
||||
tester := newTester(t, FullSync)
|
||||
defer tester.terminate()
|
||||
|
||||
chain := testChainBase.shorten(blockCacheMaxItems - 15)
|
||||
peer := tester.newPeer("corrupt", eth.ETH69, chain.blocks[1:])
|
||||
peer.corruptBodies = true
|
||||
|
||||
if err := tester.downloader.BeaconSync(chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
|
||||
t.Fatalf("failed to beacon-sync chain: %v", err)
|
||||
}
|
||||
select {
|
||||
case <-peer.dropped:
|
||||
case <-time.After(1 * time.Minute):
|
||||
t.Fatal("peer was not dropped")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,10 +16,24 @@
|
|||
|
||||
package downloader
|
||||
|
||||
import "github.com/ethereum/go-ethereum/core/types"
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
)
|
||||
|
||||
type DoneEvent struct {
|
||||
Latest *types.Header
|
||||
// SyncEventType represents the type of sync event
|
||||
type SyncEventType int
|
||||
|
||||
const (
|
||||
SyncStarted SyncEventType = iota
|
||||
SyncFailed
|
||||
SyncCompleted
|
||||
)
|
||||
|
||||
// SyncEvent represents a downloader synchronization event
|
||||
type SyncEvent struct {
|
||||
Type SyncEventType
|
||||
Mode ethconfig.SyncMode
|
||||
Err error // Set when Type is SyncFailed
|
||||
Latest *types.Header // Set when Type is SyncCompleted
|
||||
}
|
||||
type StartEvent struct{}
|
||||
type FailedEvent struct{ Err error }
|
||||
|
|
|
|||
|
|
@ -323,25 +323,32 @@ func (d *Downloader) concurrentFetch(queue typedQueue) error {
|
|||
delete(pending, res.Req.Peer)
|
||||
delete(stales, res.Req.Peer)
|
||||
|
||||
// Signal the dispatcher that the round trip is done. We'll drop the
|
||||
// peer if the data turns out to be junk.
|
||||
res.Done <- nil
|
||||
res.Req.Close()
|
||||
|
||||
// If the peer was previously banned and failed to deliver its pack
|
||||
// in a reasonable time frame, ignore its message.
|
||||
if peer := d.peers.Peer(res.Req.Peer); peer != nil {
|
||||
// Deliver the received chunk of data and check chain validity
|
||||
accepted, err := queue.deliver(peer, res)
|
||||
if errors.Is(err, errInvalidChain) {
|
||||
return err
|
||||
}
|
||||
// Unless a peer delivered something completely else than requested (usually
|
||||
// caused by a timed out request which came through in the end), set it to
|
||||
// idle. If the delivery's stale, the peer should have already been idled.
|
||||
if !errors.Is(err, errStaleDelivery) {
|
||||
queue.updateCapacity(peer, accepted, res.Time)
|
||||
}
|
||||
peer := d.peers.Peer(res.Req.Peer)
|
||||
if peer == nil {
|
||||
res.Done <- nil
|
||||
res.Req.Close()
|
||||
continue
|
||||
}
|
||||
// Deliver the received chunk of data and check chain validity
|
||||
accepted, err := queue.deliver(peer, res)
|
||||
// Unless a peer delivered something completely else than requested (usually
|
||||
// caused by a timed out request which came through in the end), set it to
|
||||
// idle. If the delivery's stale, the peer should have already been idled.
|
||||
if !errors.Is(err, errStaleDelivery) {
|
||||
queue.updateCapacity(peer, accepted, res.Time)
|
||||
}
|
||||
res.Done <- validityErrorOfRequest(err)
|
||||
res.Req.Close()
|
||||
|
||||
if errors.Is(err, errInvalidChain) {
|
||||
// errInvalidChain is the signal that processing of items failed internally,
|
||||
// even though the items were validly encoded.
|
||||
//
|
||||
// This can be due to invalid blocks, or a database error.
|
||||
// The sync cycle should be aborted for such errors, so we return it here.
|
||||
return err
|
||||
}
|
||||
|
||||
case cont := <-queue.waker():
|
||||
|
|
@ -352,3 +359,11 @@ func (d *Downloader) concurrentFetch(queue typedQueue) error {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// validityErrorOfRequest returns err if it is related to block validity, and nil otherwise.
|
||||
func validityErrorOfRequest(err error) error {
|
||||
if errors.Is(err, errInvalidBody) || errors.Is(err, errInvalidReceipt) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -671,10 +671,10 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
|
|||
}
|
||||
// Assemble each of the results with their headers and retrieved data parts
|
||||
var (
|
||||
accepted int
|
||||
failure error
|
||||
i int
|
||||
hashes []common.Hash
|
||||
accepted int
|
||||
failure error
|
||||
i int
|
||||
foundStale bool
|
||||
)
|
||||
for _, header := range request.Headers {
|
||||
// Short circuit assembly if no more fetch results are found
|
||||
|
|
@ -686,42 +686,41 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
|
|||
failure = err
|
||||
break
|
||||
}
|
||||
hashes = append(hashes, header.Hash())
|
||||
i++
|
||||
}
|
||||
|
||||
for _, header := range request.Headers[:i] {
|
||||
for k, header := range request.Headers[:i] {
|
||||
if res, stale, err := q.resultCache.GetDeliverySlot(header.Number.Uint64()); err == nil && !stale {
|
||||
reconstruct(accepted, res)
|
||||
reconstruct(k, res)
|
||||
accepted++
|
||||
} else {
|
||||
// else: between here and above, some other peer filled this result,
|
||||
// Between here and above, some other peer filled this result,
|
||||
// or it was indeed a no-op. This should not happen, but if it does it's
|
||||
// not something to panic about
|
||||
log.Error("Delivery stale", "stale", stale, "number", header.Number.Uint64(), "err", err)
|
||||
failure = errStaleDelivery
|
||||
foundStale = true
|
||||
}
|
||||
// Clean up a successful fetch
|
||||
delete(taskPool, hashes[accepted])
|
||||
accepted++
|
||||
delete(taskPool, header.Hash())
|
||||
}
|
||||
resDropMeter.Mark(int64(results - accepted))
|
||||
|
||||
// Return all failed or missing fetches to the queue
|
||||
for _, header := range request.Headers[accepted:] {
|
||||
for _, header := range request.Headers[i:] {
|
||||
taskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||
}
|
||||
// Wake up Results
|
||||
if accepted > 0 {
|
||||
q.active.Signal()
|
||||
}
|
||||
if failure == nil {
|
||||
return accepted, nil
|
||||
if failure != nil {
|
||||
return accepted, failure
|
||||
}
|
||||
// If none of the data was good, it's a stale delivery
|
||||
if accepted > 0 {
|
||||
return accepted, fmt.Errorf("partial failure: %v", failure)
|
||||
if foundStale {
|
||||
return accepted, errStaleDelivery
|
||||
}
|
||||
return accepted, fmt.Errorf("%w: %v", failure, errStaleDelivery)
|
||||
return accepted, nil
|
||||
}
|
||||
|
||||
// Prepare configures the result cache to allow accepting and caching inbound
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/miner"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
||||
)
|
||||
|
||||
|
|
@ -59,6 +60,7 @@ var Defaults = Config{
|
|||
StateHistory: pathdb.Defaults.StateHistory,
|
||||
TrienodeHistory: pathdb.Defaults.TrienodeHistory,
|
||||
NodeFullValueCheckpoint: pathdb.Defaults.FullValueCheckpoint,
|
||||
BinTrieGroupDepth: triedb.DefaultBinTrieGroupDepth,
|
||||
DatabaseCache: 2048,
|
||||
TrieCleanCache: 614,
|
||||
TrieDirtyCache: 1024,
|
||||
|
|
@ -125,6 +127,11 @@ type Config struct {
|
|||
// consistent with persistent state.
|
||||
StateScheme string `toml:",omitempty"`
|
||||
|
||||
// BinTrieGroupDepth is the number of levels per serialized group in binary trie.
|
||||
// Valid values are 1-8, with 8 being the default (byte-aligned groups).
|
||||
// Lower values create smaller groups with more nodes.
|
||||
BinTrieGroupDepth int `toml:",omitempty"`
|
||||
|
||||
// RequiredBlocks is a set of block number -> hash mappings which must be in the
|
||||
// canonical chain of all remote peers. Setting the option makes geth verify the
|
||||
// presence of these blocks for every new peer connection.
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
TrienodeHistory int64 `toml:",omitempty"`
|
||||
NodeFullValueCheckpoint uint32 `toml:",omitempty"`
|
||||
StateScheme string `toml:",omitempty"`
|
||||
BinTrieGroupDepth int `toml:",omitempty"`
|
||||
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
||||
SlowBlockThreshold time.Duration `toml:",omitempty"`
|
||||
SkipBcVersionCheck bool `toml:"-"`
|
||||
|
|
@ -87,6 +88,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
enc.TrienodeHistory = c.TrienodeHistory
|
||||
enc.NodeFullValueCheckpoint = c.NodeFullValueCheckpoint
|
||||
enc.StateScheme = c.StateScheme
|
||||
enc.BinTrieGroupDepth = c.BinTrieGroupDepth
|
||||
enc.RequiredBlocks = c.RequiredBlocks
|
||||
enc.SlowBlockThreshold = c.SlowBlockThreshold
|
||||
enc.SkipBcVersionCheck = c.SkipBcVersionCheck
|
||||
|
|
@ -144,6 +146,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
TrienodeHistory *int64 `toml:",omitempty"`
|
||||
NodeFullValueCheckpoint *uint32 `toml:",omitempty"`
|
||||
StateScheme *string `toml:",omitempty"`
|
||||
BinTrieGroupDepth *int `toml:",omitempty"`
|
||||
RequiredBlocks map[uint64]common.Hash `toml:"-"`
|
||||
SlowBlockThreshold *time.Duration `toml:",omitempty"`
|
||||
SkipBcVersionCheck *bool `toml:"-"`
|
||||
|
|
@ -234,6 +237,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
if dec.StateScheme != nil {
|
||||
c.StateScheme = *dec.StateScheme
|
||||
}
|
||||
if dec.BinTrieGroupDepth != nil {
|
||||
c.BinTrieGroupDepth = *dec.BinTrieGroupDepth
|
||||
}
|
||||
if dec.RequiredBlocks != nil {
|
||||
c.RequiredBlocks = dec.RequiredBlocks
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// Options are the contextual parameters to execute the requested call.
|
||||
|
|
@ -70,17 +70,17 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
|
|||
}
|
||||
|
||||
// Normalize the max fee per gas the call is willing to spend.
|
||||
var feeCap *big.Int
|
||||
var feeCap *uint256.Int
|
||||
if call.GasFeeCap != nil {
|
||||
feeCap = call.GasFeeCap
|
||||
} else if call.GasPrice != nil {
|
||||
feeCap = call.GasPrice
|
||||
} else {
|
||||
feeCap = common.Big0
|
||||
feeCap = uint256.NewInt(0)
|
||||
}
|
||||
// Recap the highest gas limit with account's available balance.
|
||||
if feeCap.BitLen() != 0 {
|
||||
balance := opts.State.GetBalance(call.From).ToBig()
|
||||
balance := opts.State.GetBalance(call.From).Clone()
|
||||
|
||||
available := balance
|
||||
if call.Value != nil {
|
||||
|
|
@ -90,8 +90,8 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
|
|||
available.Sub(available, call.Value)
|
||||
}
|
||||
if opts.Config.IsCancun(opts.Header.Number, opts.Header.Time) && len(call.BlobHashes) > 0 {
|
||||
blobGasPerBlob := new(big.Int).SetInt64(params.BlobTxBlobGasPerBlob)
|
||||
blobBalanceUsage := new(big.Int).SetInt64(int64(len(call.BlobHashes)))
|
||||
blobGasPerBlob := uint256.NewInt(params.BlobTxBlobGasPerBlob)
|
||||
blobBalanceUsage := uint256.NewInt(uint64(len(call.BlobHashes)))
|
||||
blobBalanceUsage.Mul(blobBalanceUsage, blobGasPerBlob)
|
||||
blobBalanceUsage.Mul(blobBalanceUsage, call.BlobGasFeeCap)
|
||||
if blobBalanceUsage.Cmp(available) >= 0 {
|
||||
|
|
@ -99,13 +99,13 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
|
|||
}
|
||||
available.Sub(available, blobBalanceUsage)
|
||||
}
|
||||
allowance := new(big.Int).Div(available, feeCap)
|
||||
allowance := new(uint256.Int).Div(available, feeCap)
|
||||
|
||||
// If the allowance is larger than maximum uint64, skip checking
|
||||
if allowance.IsUint64() && hi > allowance.Uint64() {
|
||||
transfer := call.Value
|
||||
if transfer == nil {
|
||||
transfer = new(big.Int)
|
||||
transfer = new(uint256.Int)
|
||||
}
|
||||
log.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance,
|
||||
"sent", transfer, "maxFeePerGas", feeCap, "fundable", allowance)
|
||||
|
|
|
|||
|
|
@ -107,7 +107,6 @@ type handlerConfig struct {
|
|||
Network uint64 // Network identifier to advertise
|
||||
Sync ethconfig.SyncMode // Whether to snap or full sync
|
||||
BloomCache uint64 // Megabytes to alloc for snap sync bloom
|
||||
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
|
||||
RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
|
||||
}
|
||||
|
||||
|
|
@ -126,7 +125,6 @@ type handler struct {
|
|||
peers *peerSet
|
||||
txBroadcastKey [16]byte
|
||||
|
||||
eventMux *event.TypeMux
|
||||
txsCh chan core.NewTxsEvent
|
||||
txsSub event.Subscription
|
||||
blockRange *blockRangeState
|
||||
|
|
@ -144,14 +142,9 @@ type handler struct {
|
|||
|
||||
// newHandler returns a handler for all Ethereum chain management protocol.
|
||||
func newHandler(config *handlerConfig) (*handler, error) {
|
||||
// Create the protocol manager with the base fields
|
||||
if config.EventMux == nil {
|
||||
config.EventMux = new(event.TypeMux) // Nicety initialization for tests
|
||||
}
|
||||
h := &handler{
|
||||
nodeID: config.NodeID,
|
||||
networkID: config.Network,
|
||||
eventMux: config.EventMux,
|
||||
database: config.Database,
|
||||
txpool: config.TxPool,
|
||||
chain: config.Chain,
|
||||
|
|
@ -163,7 +156,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
|||
handlerStartCh: make(chan struct{}),
|
||||
}
|
||||
// Construct the downloader (long sync)
|
||||
h.downloader = downloader.New(config.Database, config.Sync, h.eventMux, h.chain, h.removePeer, h.enableSyncedFeatures)
|
||||
h.downloader = downloader.New(config.Database, config.Sync, h.chain, h.removePeer, h.enableSyncedFeatures)
|
||||
|
||||
// If snap sync is requested but snapshots are disabled, fail loudly
|
||||
if h.downloader.ConfigSyncMode() == ethconfig.SnapSync && (config.Chain.Snapshots() == nil && config.Chain.TrieDB().Scheme() == rawdb.HashScheme) {
|
||||
|
|
@ -420,7 +413,7 @@ func (h *handler) Start(maxPeers int) {
|
|||
|
||||
// broadcast block range
|
||||
h.wg.Add(1)
|
||||
h.blockRange = newBlockRangeState(h.chain, h.eventMux)
|
||||
h.blockRange = newBlockRangeState(h.chain, h.downloader)
|
||||
go h.blockRangeLoop(h.blockRange)
|
||||
|
||||
// start sync handlers
|
||||
|
|
@ -536,16 +529,19 @@ type blockRangeState struct {
|
|||
next atomic.Pointer[eth.BlockRangeUpdatePacket]
|
||||
headCh chan core.ChainHeadEvent
|
||||
headSub event.Subscription
|
||||
syncSub *event.TypeMuxSubscription
|
||||
syncCh chan downloader.SyncEvent
|
||||
syncSub event.Subscription
|
||||
}
|
||||
|
||||
func newBlockRangeState(chain *core.BlockChain, typeMux *event.TypeMux) *blockRangeState {
|
||||
func newBlockRangeState(chain *core.BlockChain, dl *downloader.Downloader) *blockRangeState {
|
||||
headCh := make(chan core.ChainHeadEvent, chainHeadChanSize)
|
||||
headSub := chain.SubscribeChainHeadEvent(headCh)
|
||||
syncSub := typeMux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
|
||||
syncCh := make(chan downloader.SyncEvent, 16)
|
||||
syncSub := dl.SubscribeSyncEvents(syncCh)
|
||||
st := &blockRangeState{
|
||||
headCh: headCh,
|
||||
headSub: headSub,
|
||||
syncCh: syncCh,
|
||||
syncSub: syncSub,
|
||||
}
|
||||
st.update(chain, chain.CurrentBlock())
|
||||
|
|
@ -561,11 +557,8 @@ func (h *handler) blockRangeLoop(st *blockRangeState) {
|
|||
|
||||
for {
|
||||
select {
|
||||
case ev := <-st.syncSub.Chan():
|
||||
if ev == nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := ev.Data.(downloader.StartEvent); ok && h.downloader.ConfigSyncMode() == ethconfig.SnapSync {
|
||||
case ev := <-st.syncCh:
|
||||
if ev.Type == downloader.SyncStarted && ev.Mode == ethconfig.SnapSync {
|
||||
h.blockRangeWhileSnapSyncing(st)
|
||||
}
|
||||
case <-st.headCh:
|
||||
|
|
@ -593,12 +586,8 @@ func (h *handler) blockRangeWhileSnapSyncing(st *blockRangeState) {
|
|||
h.broadcastBlockRange(st)
|
||||
}
|
||||
// back to processing head block updates when sync is done
|
||||
case ev := <-st.syncSub.Chan():
|
||||
if ev == nil {
|
||||
continue
|
||||
}
|
||||
switch ev.Data.(type) {
|
||||
case downloader.FailedEvent, downloader.DoneEvent:
|
||||
case ev := <-st.syncCh:
|
||||
if ev.Type == downloader.SyncFailed || ev.Type == downloader.SyncCompleted {
|
||||
return
|
||||
}
|
||||
// ignore head updates, but exit when the subscription ends
|
||||
|
|
|
|||
|
|
@ -424,16 +424,20 @@ func testGetBlockBodies(t *testing.T, protocol uint) {
|
|||
{0, []common.Hash{backend.chain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable
|
||||
{0, []common.Hash{{}}, []bool{false}, 0}, // A non existent block should not be returned
|
||||
|
||||
// Existing and non-existing blocks interleaved should not cause problems
|
||||
// Existing blocks followed by a non-existing one should stop at the gap
|
||||
{0, []common.Hash{
|
||||
backend.chain.GetBlockByNumber(1).Hash(),
|
||||
backend.chain.GetBlockByNumber(10).Hash(),
|
||||
backend.chain.GetBlockByNumber(100).Hash(),
|
||||
{},
|
||||
}, []bool{true, true, true, false}, 3},
|
||||
|
||||
// A non-existing block at the start should return nothing
|
||||
{0, []common.Hash{
|
||||
{},
|
||||
backend.chain.GetBlockByNumber(1).Hash(),
|
||||
{},
|
||||
backend.chain.GetBlockByNumber(10).Hash(),
|
||||
{},
|
||||
backend.chain.GetBlockByNumber(100).Hash(),
|
||||
{},
|
||||
}, []bool{false, true, false, true, false, true, false}, 3},
|
||||
}, []bool{false, true, true}, 0},
|
||||
}
|
||||
// Run each of the tests and verify the results against the chain
|
||||
for i, tt := range tests {
|
||||
|
|
|
|||
|
|
@ -238,10 +238,12 @@ func ServiceGetBlockBodiesQuery(chain *core.BlockChain, query GetBlockBodiesRequ
|
|||
lookups >= 2*maxBodiesServe {
|
||||
break
|
||||
}
|
||||
if data := chain.GetBodyRLP(hash); len(data) != 0 {
|
||||
bodies = append(bodies, data)
|
||||
bytes += len(data)
|
||||
data := chain.GetBodyRLP(hash)
|
||||
if len(data) == 0 {
|
||||
break // If we don't have this block's body, stop serving.
|
||||
}
|
||||
bodies = append(bodies, data)
|
||||
bytes += len(data)
|
||||
}
|
||||
return bodies
|
||||
}
|
||||
|
|
@ -281,16 +283,16 @@ func ServiceGetReceiptsQuery69(chain *core.BlockChain, query GetReceiptsRequest)
|
|||
// Retrieve the requested block's receipts
|
||||
results := chain.GetReceiptsRLP(hash)
|
||||
if results == nil {
|
||||
continue // Can't retrieve the receipts, so we just skip this block.
|
||||
break // Don't have this block's receipts, stop serving.
|
||||
}
|
||||
body := chain.GetBodyRLP(hash)
|
||||
if body == nil {
|
||||
continue // The block body is missing, we also have to skip.
|
||||
break // The block body is missing, stop serving.
|
||||
}
|
||||
results, _, err := blockReceiptsToNetwork(results, body, receiptQueryParams{})
|
||||
if err != nil {
|
||||
log.Error("Error in block receipts conversion", "hash", hash, "err", err)
|
||||
continue
|
||||
break
|
||||
}
|
||||
receipts.AppendRaw(results)
|
||||
bytes += len(results)
|
||||
|
|
@ -312,12 +314,13 @@ func serviceGetReceiptsQuery70(chain *core.BlockChain, query GetReceiptsRequest,
|
|||
break
|
||||
}
|
||||
results := chain.GetReceiptsRLP(hash)
|
||||
// If we don't have this block's receipts or body, stop serving.
|
||||
if results == nil {
|
||||
continue // Can't retrieve the receipts, so we just skip this block.
|
||||
break
|
||||
}
|
||||
body := chain.GetBodyRLP(hash)
|
||||
if body == nil {
|
||||
continue // The block body is missing, we also have to skip.
|
||||
break
|
||||
}
|
||||
q := receiptQueryParams{sizeLimit: uint64(maxPacketSize - bytes)}
|
||||
if i == 0 {
|
||||
|
|
@ -326,7 +329,7 @@ func serviceGetReceiptsQuery70(chain *core.BlockChain, query GetReceiptsRequest,
|
|||
results, incomplete, err := blockReceiptsToNetwork(results, body, q)
|
||||
if err != nil {
|
||||
log.Error("Error in block receipts conversion", "hash", hash, "err", err)
|
||||
continue
|
||||
break
|
||||
}
|
||||
if results == nil {
|
||||
// This case triggers when the first receipt of the block receipts list doesn't
|
||||
|
|
|
|||
|
|
@ -248,13 +248,10 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
|
|||
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
|
||||
evm := vm.NewEVM(context, statedb, eth.blockchain.Config(), vm.Config{})
|
||||
defer evm.Release()
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
// If prague hardfork, insert parent block hash in the state as per EIP-2935.
|
||||
if eth.blockchain.Config().IsPrague(block.Number(), block.Time()) {
|
||||
core.ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
}
|
||||
|
||||
// Run pre-execution system calls
|
||||
core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), eth.blockchain.Config(), evm, block.Number(), block.Time())
|
||||
|
||||
if txIndex == 0 && len(block.Transactions()) == 0 {
|
||||
return nil, context, statedb, release, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
|
|
@ -37,32 +38,40 @@ type syncReq struct {
|
|||
errc chan error
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
TargetBlock common.Hash // if set, sync is triggered at startup
|
||||
ExitWhenSynced bool // if true, the node shuts down after sync has finished
|
||||
}
|
||||
|
||||
// Syncer is an auxiliary service that allows Geth to perform full sync
|
||||
// alone without consensus-layer attached. Users must specify a valid block hash
|
||||
// as the sync target.
|
||||
//
|
||||
// Additionally, the syncer can be used to monitor state synchronization.
|
||||
// It will exit once the specified target has been reached or when the
|
||||
// most recent chain head is caught up.
|
||||
//
|
||||
// This tool can be applied to different networks, no matter it's pre-merge or
|
||||
// post-merge, but only for full-sync.
|
||||
type Syncer struct {
|
||||
stack *node.Node
|
||||
backend *eth.Ethereum
|
||||
target common.Hash
|
||||
request chan *syncReq
|
||||
closed chan struct{}
|
||||
wg sync.WaitGroup
|
||||
exitWhenSynced bool
|
||||
stack *node.Node
|
||||
backend *eth.Ethereum
|
||||
request chan *syncReq
|
||||
closed chan struct{}
|
||||
wg sync.WaitGroup
|
||||
|
||||
config Config
|
||||
}
|
||||
|
||||
// Register registers the synchronization override service into the node
|
||||
// stack for launching and stopping the service controlled by node.
|
||||
func Register(stack *node.Node, backend *eth.Ethereum, target common.Hash, exitWhenSynced bool) (*Syncer, error) {
|
||||
func Register(stack *node.Node, backend *eth.Ethereum, cfg Config) (*Syncer, error) {
|
||||
s := &Syncer{
|
||||
stack: stack,
|
||||
backend: backend,
|
||||
target: target,
|
||||
request: make(chan *syncReq),
|
||||
closed: make(chan struct{}),
|
||||
exitWhenSynced: exitWhenSynced,
|
||||
stack: stack,
|
||||
backend: backend,
|
||||
request: make(chan *syncReq),
|
||||
closed: make(chan struct{}),
|
||||
config: cfg,
|
||||
}
|
||||
stack.RegisterAPIs(s.APIs())
|
||||
stack.RegisterLifecycle(s)
|
||||
|
|
@ -88,9 +97,11 @@ func (s *Syncer) run() {
|
|||
|
||||
var (
|
||||
target *types.Header
|
||||
ticker = time.NewTicker(time.Second * 5)
|
||||
syncCh = make(chan downloader.SyncEvent, 10)
|
||||
)
|
||||
defer ticker.Stop()
|
||||
sub := s.backend.Downloader().SubscribeSyncEvents(syncCh)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
for {
|
||||
select {
|
||||
case req := <-s.request:
|
||||
|
|
@ -137,35 +148,50 @@ func (s *Syncer) run() {
|
|||
}
|
||||
}
|
||||
|
||||
case <-ticker.C:
|
||||
if target == nil {
|
||||
case ev := <-syncCh:
|
||||
if ev.Type == downloader.SyncStarted {
|
||||
log.Debug("Synchronization started")
|
||||
continue
|
||||
}
|
||||
if ev.Type == downloader.SyncFailed {
|
||||
log.Debug("Synchronization failed", "err", ev.Err)
|
||||
continue
|
||||
}
|
||||
|
||||
head := s.backend.BlockChain().CurrentHeader()
|
||||
if head != nil {
|
||||
// Set the finalized and safe markers relative to the current head.
|
||||
// The finalized marker is set two epochs behind the target,
|
||||
// and the safe marker is set one epoch behind the target.
|
||||
if header := s.backend.BlockChain().GetHeaderByNumber(head.Number.Uint64() - params.EpochLength*2); header != nil {
|
||||
if final := s.backend.BlockChain().CurrentFinalBlock(); final == nil || final.Number.Cmp(header.Number) < 0 {
|
||||
s.backend.BlockChain().SetFinalized(header)
|
||||
}
|
||||
}
|
||||
if header := s.backend.BlockChain().GetHeaderByNumber(head.Number.Uint64() - params.EpochLength); header != nil {
|
||||
if safe := s.backend.BlockChain().CurrentSafeBlock(); safe == nil || safe.Number.Cmp(header.Number) < 0 {
|
||||
s.backend.BlockChain().SetSafe(header)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Terminate the node if the target has been reached
|
||||
if s.exitWhenSynced {
|
||||
if block := s.backend.BlockChain().GetBlockByHash(target.Hash()); block != nil {
|
||||
log.Info("Sync target reached", "number", block.NumberU64(), "hash", block.Hash())
|
||||
go s.stack.Close() // async since we need to close ourselves
|
||||
return
|
||||
if s.config.ExitWhenSynced {
|
||||
var synced bool
|
||||
var block *types.Header
|
||||
if target != nil {
|
||||
tb := s.backend.BlockChain().GetBlockByHash(target.Hash())
|
||||
synced = tb != nil
|
||||
block = tb.Header()
|
||||
} else {
|
||||
timestamp := time.Unix(int64(ev.Latest.Time), 0)
|
||||
synced = time.Since(timestamp) < 10*time.Minute
|
||||
block = ev.Latest
|
||||
}
|
||||
}
|
||||
|
||||
// Set the finalized and safe markers relative to the current head.
|
||||
// The finalized marker is set two epochs behind the target,
|
||||
// and the safe marker is set one epoch behind the target.
|
||||
head := s.backend.BlockChain().CurrentHeader()
|
||||
if head == nil {
|
||||
continue
|
||||
}
|
||||
if header := s.backend.BlockChain().GetHeaderByNumber(head.Number.Uint64() - params.EpochLength*2); header != nil {
|
||||
if final := s.backend.BlockChain().CurrentFinalBlock(); final == nil || final.Number.Cmp(header.Number) < 0 {
|
||||
s.backend.BlockChain().SetFinalized(header)
|
||||
}
|
||||
}
|
||||
if header := s.backend.BlockChain().GetHeaderByNumber(head.Number.Uint64() - params.EpochLength); header != nil {
|
||||
if safe := s.backend.BlockChain().CurrentSafeBlock(); safe == nil || safe.Number.Cmp(header.Number) < 0 {
|
||||
s.backend.BlockChain().SetSafe(header)
|
||||
if synced {
|
||||
log.Info("Sync target reached", "number", block.Number.Uint64(), "hash", block.Hash())
|
||||
go s.stack.Close() // async since we need to close ourselves
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -179,10 +205,10 @@ func (s *Syncer) run() {
|
|||
func (s *Syncer) Start() error {
|
||||
s.wg.Add(1)
|
||||
go s.run()
|
||||
if s.target == (common.Hash{}) {
|
||||
if s.config.TargetBlock == (common.Hash{}) {
|
||||
return nil
|
||||
}
|
||||
return s.Sync(s.target)
|
||||
return s.Sync(s.config.TargetBlock)
|
||||
}
|
||||
|
||||
// Stop terminates the synchronization service and stop all background activities.
|
||||
|
|
|
|||
|
|
@ -372,13 +372,8 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
|||
// as per EIP-4788.
|
||||
context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil)
|
||||
evm := vm.NewEVM(context, statedb, api.backend.ChainConfig(), vm.Config{})
|
||||
if beaconRoot := next.BeaconRoot(); beaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
// Insert parent hash in history contract.
|
||||
if api.backend.ChainConfig().IsPrague(next.Number(), next.Time()) {
|
||||
core.ProcessParentBlockHash(next.ParentHash(), evm)
|
||||
}
|
||||
|
||||
core.PreExecution(ctx, next.BeaconRoot(), next.ParentHash(), api.backend.ChainConfig(), evm, next.Number(), next.Time())
|
||||
evm.Release()
|
||||
// Clean out any pending release functions of trace state. Note this
|
||||
// step must be done after constructing tracing state, because the
|
||||
|
|
@ -494,8 +489,8 @@ func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash,
|
|||
return api.standardTraceBlockToFile(ctx, block, config)
|
||||
}
|
||||
|
||||
// IntermediateRoots executes a block (bad- or canon- or side-), and returns a list
|
||||
// of intermediate roots: the stateroot after each transaction.
|
||||
// IntermediateRoots executes a block, and returns a list of intermediate roots:
|
||||
// the stateroot after each transaction.
|
||||
func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config *TraceConfig) ([]common.Hash, error) {
|
||||
block, _ := api.blockByHash(ctx, hash)
|
||||
if block == nil {
|
||||
|
|
@ -517,21 +512,19 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
|
|||
return nil, err
|
||||
}
|
||||
defer release()
|
||||
|
||||
var (
|
||||
roots []common.Hash
|
||||
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
||||
chainConfig = api.backend.ChainConfig()
|
||||
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
|
||||
evm = vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
|
||||
)
|
||||
evm := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
|
||||
defer evm.Release()
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
if chainConfig.IsPrague(block.Number(), block.Time()) {
|
||||
core.ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
}
|
||||
// Run pre-execution system calls
|
||||
core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), chainConfig, evm, block.Number(), block.Time())
|
||||
|
||||
for i, tx := range block.Transactions() {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -548,7 +541,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
|
|||
// N.B: This should never happen while tracing canon blocks, only when tracing bad blocks.
|
||||
return roots, nil
|
||||
}
|
||||
// calling IntermediateRoot will internally call Finalize on the state
|
||||
// Calling IntermediateRoot will internally call Finalize on the state
|
||||
// so any modifications are written to the trie
|
||||
roots = append(roots, statedb.IntermediateRoot(deleteEmptyObjects))
|
||||
}
|
||||
|
|
@ -587,12 +580,9 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
|||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
evm := vm.NewEVM(blockCtx, statedb, api.backend.ChainConfig(), vm.Config{})
|
||||
defer evm.Release()
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
if api.backend.ChainConfig().IsPrague(block.Number(), block.Time()) {
|
||||
core.ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
}
|
||||
|
||||
// Run pre-execution system calls
|
||||
core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), api.backend.ChainConfig(), evm, block.Number(), block.Time())
|
||||
|
||||
// JS tracers have high overhead. In this case run a parallel
|
||||
// process that generates states in one thread and traces txes
|
||||
|
|
@ -760,15 +750,12 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
|||
// Note: This copies the config, to not screw up the main config
|
||||
chainConfig, canon = overrideConfig(chainConfig, config.Overrides)
|
||||
}
|
||||
|
||||
evm := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
|
||||
defer evm.Release()
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
if chainConfig.IsPrague(block.Number(), block.Time()) {
|
||||
core.ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
}
|
||||
|
||||
// Run pre-execution system calls
|
||||
core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), chainConfig, evm, block.Number(), block.Time())
|
||||
|
||||
for i, tx := range block.Transactions() {
|
||||
// Prepare the transaction for un-traced execution
|
||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
|
|
@ -795,6 +782,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
|||
return nil, err
|
||||
}
|
||||
dumps = append(dumps, dump.Name())
|
||||
|
||||
// Set up the tracer and EVM for the transaction.
|
||||
var (
|
||||
writer = bufio.NewWriter(dump)
|
||||
|
|
|
|||
|
|
@ -112,9 +112,10 @@ type AccessListTracer struct {
|
|||
func NewAccessListTracer(acl types.AccessList, addressesToExclude map[common.Address]struct{}) *AccessListTracer {
|
||||
list := newAccessList()
|
||||
for _, al := range acl {
|
||||
if _, ok := addressesToExclude[al.Address]; !ok {
|
||||
list.addAddress(al.Address)
|
||||
if _, ok := addressesToExclude[al.Address]; ok {
|
||||
continue
|
||||
}
|
||||
list.addAddress(al.Address)
|
||||
for _, slot := range al.StorageKeys {
|
||||
list.addSlot(al.Address, slot)
|
||||
}
|
||||
|
|
|
|||
39
eth/tracers/logger/access_list_tracer_test.go
Normal file
39
eth/tracers/logger/access_list_tracer_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -229,9 +229,9 @@ type StructLogger struct {
|
|||
logs []json.RawMessage // buffer of json-encoded logs
|
||||
resultSize int
|
||||
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
skip bool // skip processing hooks.
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
|
||||
skip bool // skip processing hooks.
|
||||
}
|
||||
|
||||
// NewStreamingStructLogger returns a new streaming logger.
|
||||
|
|
@ -357,8 +357,8 @@ func (l *StructLogger) OnExit(depth int, output []byte, gasUsed uint64, err erro
|
|||
|
||||
func (l *StructLogger) GetResult() (json.RawMessage, error) {
|
||||
// Tracing aborted
|
||||
if l.reason != nil {
|
||||
return nil, l.reason
|
||||
if p := l.reason.Load(); p != nil {
|
||||
return nil, *p
|
||||
}
|
||||
failed := l.err != nil
|
||||
returnData := common.CopyBytes(l.output)
|
||||
|
|
@ -376,7 +376,7 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
|
|||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (l *StructLogger) Stop(err error) {
|
||||
l.reason = err
|
||||
l.reason.Store(&err)
|
||||
l.interrupt.Store(true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ func init() {
|
|||
// 0xc281d19e-0: 1
|
||||
// }
|
||||
type fourByteTracer struct {
|
||||
ids map[string]int // ids aggregates the 4byte ids found
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
ids map[string]int // ids aggregates the 4byte ids found
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
|
||||
chainConfig *params.ChainConfig
|
||||
activePrecompiles []common.Address // Updated on tx start based on given rules
|
||||
}
|
||||
|
|
@ -124,12 +124,15 @@ func (t *fourByteTracer) GetResult() (json.RawMessage, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, t.reason
|
||||
if p := t.reason.Load(); p != nil {
|
||||
return res, *p
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (t *fourByteTracer) Stop(err error) {
|
||||
t.reason = err
|
||||
t.reason.Store(&err)
|
||||
t.interrupt.Store(true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ type callTracer struct {
|
|||
config callTracerConfig
|
||||
gasLimit uint64
|
||||
depth int
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
|
||||
}
|
||||
|
||||
type callTracerConfig struct {
|
||||
|
|
@ -268,12 +268,15 @@ func (t *callTracer) GetResult() (json.RawMessage, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, t.reason
|
||||
if p := t.reason.Load(); p != nil {
|
||||
return res, *p
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (t *callTracer) Stop(err error) {
|
||||
t.reason = err
|
||||
t.reason.Store(&err)
|
||||
t.interrupt.Store(true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -233,7 +233,10 @@ func (t *flatCallTracer) GetResult() (json.RawMessage, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, t.tracer.reason
|
||||
if p := t.tracer.reason.Load(); p != nil {
|
||||
return res, *p
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
|
|
|
|||
|
|
@ -135,8 +135,8 @@ type opcodeWithPartialStack struct {
|
|||
type erc7562Tracer struct {
|
||||
config erc7562TracerConfig
|
||||
gasLimit uint64
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
|
||||
env *tracing.VMContext
|
||||
|
||||
ignoredOpcodes map[vm.OpCode]struct{}
|
||||
|
|
@ -317,7 +317,10 @@ func (t *erc7562Tracer) OnLog(log1 *types.Log) {
|
|||
// error arising from the encoding or forceful termination (via `Stop`).
|
||||
func (t *erc7562Tracer) GetResult() (json.RawMessage, error) {
|
||||
if t.interrupt.Load() {
|
||||
return nil, t.reason
|
||||
if p := t.reason.Load(); p != nil {
|
||||
return nil, *p
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
if len(t.callstackWithOpcodes) != 1 {
|
||||
return nil, errors.New("incorrect number of top-level calls")
|
||||
|
|
@ -337,12 +340,15 @@ func (t *erc7562Tracer) GetResult() (json.RawMessage, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return enc, t.reason
|
||||
if p := t.reason.Load(); p != nil {
|
||||
return enc, *p
|
||||
}
|
||||
return enc, nil
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (t *erc7562Tracer) Stop(err error) {
|
||||
t.reason = err
|
||||
t.reason.Store(&err)
|
||||
t.interrupt.Store(true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,22 +63,30 @@ func newMuxTracerFromConfig(ctx *tracers.Context, cfg json.RawMessage, chainConf
|
|||
//
|
||||
// The names parameter associates a label with each tracer, used as keys in
|
||||
// the aggregated JSON result returned by GetResult.
|
||||
//
|
||||
// For hooks that have both a V1 and V2 form (OnCodeChange / OnCodeChangeV2,
|
||||
// OnNonceChange / OnNonceChangeV2, OnSystemCallStart / OnSystemCallStartV2),
|
||||
// the mux exposes only the V2 variant upward. The fanout then prefers each
|
||||
// child's V2 hook and falls back to V1 if only V1 is set, mirroring the
|
||||
// precedence already used in core/state_processor.go.
|
||||
func NewMuxTracer(names []string, objects []*tracers.Tracer) (*tracers.Tracer, error) {
|
||||
t := &muxTracer{names: names, tracers: objects}
|
||||
return &tracers.Tracer{
|
||||
Hooks: &tracing.Hooks{
|
||||
OnTxStart: t.OnTxStart,
|
||||
OnTxEnd: t.OnTxEnd,
|
||||
OnEnter: t.OnEnter,
|
||||
OnExit: t.OnExit,
|
||||
OnOpcode: t.OnOpcode,
|
||||
OnFault: t.OnFault,
|
||||
OnGasChange: t.OnGasChange,
|
||||
OnBalanceChange: t.OnBalanceChange,
|
||||
OnNonceChange: t.OnNonceChange,
|
||||
OnCodeChange: t.OnCodeChange,
|
||||
OnStorageChange: t.OnStorageChange,
|
||||
OnLog: t.OnLog,
|
||||
OnTxStart: t.OnTxStart,
|
||||
OnTxEnd: t.OnTxEnd,
|
||||
OnEnter: t.OnEnter,
|
||||
OnExit: t.OnExit,
|
||||
OnOpcode: t.OnOpcode,
|
||||
OnFault: t.OnFault,
|
||||
OnGasChange: t.OnGasChange,
|
||||
OnBalanceChange: t.OnBalanceChange,
|
||||
OnNonceChangeV2: t.OnNonceChangeV2,
|
||||
OnCodeChangeV2: t.OnCodeChangeV2,
|
||||
OnStorageChange: t.OnStorageChange,
|
||||
OnLog: t.OnLog,
|
||||
OnSystemCallStartV2: t.OnSystemCallStart,
|
||||
OnSystemCallEnd: t.OnSystemCallEnd,
|
||||
},
|
||||
GetResult: t.GetResult,
|
||||
Stop: t.Stop,
|
||||
|
|
@ -149,26 +157,22 @@ func (t *muxTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason
|
|||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnNonceChange(a common.Address, prev, new uint64) {
|
||||
func (t *muxTracer) OnNonceChangeV2(a common.Address, prev, new uint64, reason tracing.NonceChangeReason) {
|
||||
for _, t := range t.tracers {
|
||||
if t.OnNonceChange != nil {
|
||||
if t.OnNonceChangeV2 != nil {
|
||||
t.OnNonceChangeV2(a, prev, new, reason)
|
||||
} else if t.OnNonceChange != nil {
|
||||
t.OnNonceChange(a, prev, new)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
|
||||
for _, t := range t.tracers {
|
||||
if t.OnCodeChange != nil {
|
||||
t.OnCodeChange(a, prevCodeHash, prev, codeHash, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnCodeChangeV2(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte, reason tracing.CodeChangeReason) {
|
||||
for _, t := range t.tracers {
|
||||
if t.OnCodeChangeV2 != nil {
|
||||
t.OnCodeChangeV2(a, prevCodeHash, prev, codeHash, code, reason)
|
||||
} else if t.OnCodeChange != nil {
|
||||
t.OnCodeChange(a, prevCodeHash, prev, codeHash, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -189,6 +193,24 @@ func (t *muxTracer) OnLog(log *types.Log) {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnSystemCallStart(vm *tracing.VMContext) {
|
||||
for _, t := range t.tracers {
|
||||
if t.OnSystemCallStartV2 != nil {
|
||||
t.OnSystemCallStartV2(vm)
|
||||
} else if t.OnSystemCallStart != nil {
|
||||
t.OnSystemCallStart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) OnSystemCallEnd() {
|
||||
for _, t := range t.tracers {
|
||||
if t.OnSystemCallEnd != nil {
|
||||
t.OnSystemCallEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetResult returns an empty json object.
|
||||
func (t *muxTracer) GetResult() (json.RawMessage, error) {
|
||||
resObject := make(map[string]json.RawMessage)
|
||||
|
|
|
|||
87
eth/tracers/native/mux_test.go
Normal file
87
eth/tracers/native/mux_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -71,8 +71,8 @@ type prestateTracer struct {
|
|||
to common.Address
|
||||
config PrestateTracerConfig
|
||||
chainConfig *params.ChainConfig
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason atomic.Pointer[error] // Reason for the interruption, populated by Stop
|
||||
created map[common.Address]bool
|
||||
deleted map[common.Address]bool
|
||||
}
|
||||
|
|
@ -240,12 +240,15 @@ func (t *prestateTracer) GetResult() (json.RawMessage, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.RawMessage(res), t.reason
|
||||
if p := t.reason.Load(); p != nil {
|
||||
return json.RawMessage(res), *p
|
||||
}
|
||||
return json.RawMessage(res), nil
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (t *prestateTracer) Stop(err error) {
|
||||
t.reason = err
|
||||
t.reason.Store(&err)
|
||||
t.interrupt.Store(true)
|
||||
}
|
||||
|
||||
|
|
|
|||
80
eth/tracers/native/tracer_test.go
Normal file
80
eth/tracers/native/tracer_test.go
Normal 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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -914,6 +914,7 @@ type SimulateCallResult struct {
|
|||
ReturnValue []byte `json:"returnData"`
|
||||
Logs []*types.Log `json:"logs"`
|
||||
GasUsed uint64 `json:"gasUsed"`
|
||||
MaxUsedGas uint64 `json:"maxUsedGas"`
|
||||
Status uint64 `json:"status"`
|
||||
Error *CallError `json:"error,omitempty"`
|
||||
}
|
||||
|
|
@ -921,6 +922,7 @@ type SimulateCallResult struct {
|
|||
type simulateCallResultMarshaling struct {
|
||||
ReturnValue hexutil.Bytes
|
||||
GasUsed hexutil.Uint64
|
||||
MaxUsedGas hexutil.Uint64
|
||||
Status hexutil.Uint64
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -861,6 +861,12 @@ func TestSimulateV1(t *testing.T) {
|
|||
if results[0].Calls[0].Error != nil {
|
||||
t.Errorf("expected no error, got %v", results[0].Calls[0].Error)
|
||||
}
|
||||
if results[0].Calls[0].MaxUsedGas == 0 {
|
||||
t.Error("expected maxUsedGas to be set")
|
||||
}
|
||||
if results[0].Calls[0].MaxUsedGas < results[0].Calls[0].GasUsed {
|
||||
t.Errorf("expected maxUsedGas >= gasUsed, got %d < %d", results[0].Calls[0].MaxUsedGas, results[0].Calls[0].GasUsed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimulateV1WithBlockOverrides(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ func (s SimulateCallResult) MarshalJSON() ([]byte, error) {
|
|||
ReturnValue hexutil.Bytes `json:"returnData"`
|
||||
Logs []*types.Log `json:"logs"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed"`
|
||||
MaxUsedGas hexutil.Uint64 `json:"maxUsedGas"`
|
||||
Status hexutil.Uint64 `json:"status"`
|
||||
Error *CallError `json:"error,omitempty"`
|
||||
}
|
||||
|
|
@ -24,6 +25,7 @@ func (s SimulateCallResult) MarshalJSON() ([]byte, error) {
|
|||
enc.ReturnValue = s.ReturnValue
|
||||
enc.Logs = s.Logs
|
||||
enc.GasUsed = hexutil.Uint64(s.GasUsed)
|
||||
enc.MaxUsedGas = hexutil.Uint64(s.MaxUsedGas)
|
||||
enc.Status = hexutil.Uint64(s.Status)
|
||||
enc.Error = s.Error
|
||||
return json.Marshal(&enc)
|
||||
|
|
@ -35,6 +37,7 @@ func (s *SimulateCallResult) UnmarshalJSON(input []byte) error {
|
|||
ReturnValue *hexutil.Bytes `json:"returnData"`
|
||||
Logs []*types.Log `json:"logs"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed"`
|
||||
MaxUsedGas *hexutil.Uint64 `json:"maxUsedGas"`
|
||||
Status *hexutil.Uint64 `json:"status"`
|
||||
Error *CallError `json:"error,omitempty"`
|
||||
}
|
||||
|
|
@ -51,6 +54,9 @@ func (s *SimulateCallResult) UnmarshalJSON(input []byte) error {
|
|||
if dec.GasUsed != nil {
|
||||
s.GasUsed = uint64(*dec.GasUsed)
|
||||
}
|
||||
if dec.MaxUsedGas != nil {
|
||||
s.MaxUsedGas = uint64(*dec.MaxUsedGas)
|
||||
}
|
||||
if dec.Status != nil {
|
||||
s.Status = uint64(*dec.Status)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,8 @@ func NewBackend(alloc types.GenesisAlloc, options ...func(nodeConf *node.Config,
|
|||
}
|
||||
ethConf.SyncMode = ethconfig.FullSync
|
||||
ethConf.TxPool.NoLocals = true
|
||||
// Disable log indexing to force unindexed log search
|
||||
ethConf.LogNoHistory = true
|
||||
|
||||
for _, option := range options {
|
||||
option(&nodeConf, ðConf)
|
||||
|
|
|
|||
|
|
@ -41,12 +41,19 @@ type GoToolchain struct {
|
|||
func (g *GoToolchain) Go(command string, args ...string) *exec.Cmd {
|
||||
tool := g.goTool(command, args...)
|
||||
|
||||
// Configure environment for cross build.
|
||||
if g.GOARCH != "" && g.GOARCH != runtime.GOARCH {
|
||||
// Configure environment for cross build. Force CGO_ENABLED=1 whenever
|
||||
// either GOOS or GOARCH differs from the host: Go's default is
|
||||
// CGO_ENABLED=0 for any cross-compile, but geth's release builds rely
|
||||
// on cgo (c-kzg-4844, secp256k1) regardless of which axis is crossing.
|
||||
crossArch := g.GOARCH != "" && g.GOARCH != runtime.GOARCH
|
||||
crossOS := g.GOOS != "" && g.GOOS != runtime.GOOS
|
||||
if crossArch || crossOS {
|
||||
tool.Env = append(tool.Env, "CGO_ENABLED=1")
|
||||
}
|
||||
if crossArch {
|
||||
tool.Env = append(tool.Env, "GOARCH="+g.GOARCH)
|
||||
}
|
||||
if g.GOOS != "" && g.GOOS != runtime.GOOS {
|
||||
if crossOS {
|
||||
tool.Env = append(tool.Env, "GOOS="+g.GOOS)
|
||||
}
|
||||
// Configure C compiler.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"iter"
|
||||
|
|
@ -180,12 +181,13 @@ func (db *ChecksumDB) DownloadFile(url, dstPath string) error {
|
|||
return fmt.Errorf("no known hash for file %q", basename)
|
||||
}
|
||||
// Shortcut if already downloaded.
|
||||
if verifyHash(dstPath, hash) == nil {
|
||||
if err := verifyHash(dstPath, hash); err == nil {
|
||||
fmt.Printf("%s is up-to-date\n", dstPath)
|
||||
return nil
|
||||
} else if !errors.Is(err, os.ErrNotExist) {
|
||||
fmt.Printf("%s is stale\n", dstPath)
|
||||
}
|
||||
|
||||
fmt.Printf("%s is stale\n", dstPath)
|
||||
fmt.Printf("downloading from %s\n", url)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
|
|
@ -209,9 +211,12 @@ func (db *ChecksumDB) DownloadFile(url, dstPath string) error {
|
|||
if resp.ContentLength > 0 {
|
||||
dst = newDownloadWriter(fd, resp.ContentLength)
|
||||
}
|
||||
_, err = io.Copy(dst, resp.Body)
|
||||
dst.Close()
|
||||
if err != nil {
|
||||
if _, err = io.Copy(dst, resp.Body); err != nil {
|
||||
dst.Close()
|
||||
os.Remove(tmpfile)
|
||||
return err
|
||||
}
|
||||
if err = dst.Close(); err != nil {
|
||||
os.Remove(tmpfile)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,22 +156,22 @@ func (it *RawIterator) Next() bool {
|
|||
var n int64
|
||||
if it.Header, n, it.err = newSnappyReader(it.e.s, era.TypeCompressedHeader, off); it.err != nil {
|
||||
it.clear()
|
||||
return true
|
||||
return false
|
||||
}
|
||||
off += n
|
||||
if it.Body, n, it.err = newSnappyReader(it.e.s, era.TypeCompressedBody, off); it.err != nil {
|
||||
it.clear()
|
||||
return true
|
||||
return false
|
||||
}
|
||||
off += n
|
||||
if it.Receipts, n, it.err = newSnappyReader(it.e.s, era.TypeCompressedReceipts, off); it.err != nil {
|
||||
it.clear()
|
||||
return true
|
||||
return false
|
||||
}
|
||||
off += n
|
||||
if it.TotalDifficulty, _, it.err = it.e.s.ReaderAt(era.TypeTotalDifficulty, off); it.err != nil {
|
||||
it.clear()
|
||||
return true
|
||||
return false
|
||||
}
|
||||
it.next += 1
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -734,6 +734,10 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
|
|||
if err := blockOverrides.Apply(&blockCtx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Override the header so callers that compute gas price from 1559 fee
|
||||
// fields see the overridden basefee. Otherwise GASPRICE/effectiveTip
|
||||
// would be derived from the pre-override basefee.
|
||||
header = blockOverrides.MakeHeader(header)
|
||||
}
|
||||
rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time)
|
||||
precompiles := vm.ActivePrecompiledContracts(rules)
|
||||
|
|
@ -992,6 +996,9 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} {
|
|||
if head.RequestsHash != nil {
|
||||
result["requestsHash"] = head.RequestsHash
|
||||
}
|
||||
if head.BlockAccessListHash != nil {
|
||||
result["balHash"] = head.BlockAccessListHash
|
||||
}
|
||||
if head.SlotNumber != nil {
|
||||
result["slotNumber"] = hexutil.Uint64(*head.SlotNumber)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1315,6 +1315,27 @@ func TestCall(t *testing.T) {
|
|||
},
|
||||
expectErr: errors.New(`block override "withdrawals" is not supported for this RPC method`),
|
||||
},
|
||||
// Verify that an overridden basefee is honored when computing gasPrice
|
||||
// from the 1559 fee fields. Returning GASPRICE opcode; expected value
|
||||
// is min(MaxFeePerGas, MaxPriorityFeePerGas + overridden BaseFee).
|
||||
//
|
||||
// BaseFee override = 0xa (10); MaxFeePerGas = 0x64 (100);
|
||||
// MaxPriorityFeePerGas = 0x2 (2); expected GASPRICE = 12.
|
||||
{
|
||||
name: "basefee-override-used-in-gasprice",
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
call: TransactionArgs{
|
||||
From: &accounts[0].addr,
|
||||
// Contract: GASPRICE; PUSH1 0; MSTORE; PUSH1 32; PUSH1 0; RETURN
|
||||
Input: hex2Bytes("3a60005260206000f3"),
|
||||
MaxFeePerGas: (*hexutil.Big)(big.NewInt(100)),
|
||||
MaxPriorityFeePerGas: (*hexutil.Big)(big.NewInt(2)),
|
||||
},
|
||||
blockOverrides: override.BlockOverrides{
|
||||
BaseFeePerGas: (*hexutil.Big)(big.NewInt(10)),
|
||||
},
|
||||
want: "0x000000000000000000000000000000000000000000000000000000000000000c",
|
||||
},
|
||||
}
|
||||
for _, tc := range testSuite {
|
||||
result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides)
|
||||
|
|
@ -2659,6 +2680,67 @@ func TestSimulateV1TxSender(t *testing.T) {
|
|||
require.Equal(t, sender2, summary[1].Transactions[0].From, "sender address mismatch")
|
||||
}
|
||||
|
||||
// TestSimulateV1WithdrawalsByFork verifies that withdrawals and withdrawalsRoot
|
||||
// are only emitted in the simulated block result when the simulated block is
|
||||
// post-Shanghai. Pre-Shanghai blocks must omit both fields, otherwise the
|
||||
// header hash and size would not match a valid pre-Shanghai block.
|
||||
func TestSimulateV1WithdrawalsByFork(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
run := func(t *testing.T, cfg *params.ChainConfig, blockTime *uint64, wantWithdrawals bool) {
|
||||
t.Helper()
|
||||
gspec := &core.Genesis{Config: cfg, Alloc: types.GenesisAlloc{}}
|
||||
backend := newTestBackend(t, 1, gspec, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {})
|
||||
|
||||
ctx := context.Background()
|
||||
stateDB, baseHeader, err := backend.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get state and header: %v", err)
|
||||
}
|
||||
sim := &simulator{
|
||||
b: backend,
|
||||
state: stateDB,
|
||||
base: baseHeader,
|
||||
chainConfig: backend.ChainConfig(),
|
||||
budget: newGasBudget(0),
|
||||
}
|
||||
|
||||
block := simBlock{}
|
||||
if blockTime != nil {
|
||||
t := hexutil.Uint64(*blockTime)
|
||||
block.BlockOverrides = &override.BlockOverrides{Time: &t}
|
||||
}
|
||||
results, err := sim.execute(ctx, []simBlock{block})
|
||||
if err != nil {
|
||||
t.Fatalf("simulation execution failed: %v", err)
|
||||
}
|
||||
require.Len(t, results, 1)
|
||||
|
||||
enc, err := json.Marshal(results[0])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal result: %v", err)
|
||||
}
|
||||
var raw map[string]json.RawMessage
|
||||
if err := json.Unmarshal(enc, &raw); err != nil {
|
||||
t.Fatalf("failed to unmarshal result: %v", err)
|
||||
}
|
||||
_, hasWithdrawals := raw["withdrawals"]
|
||||
_, hasWithdrawalsRoot := raw["withdrawalsRoot"]
|
||||
if hasWithdrawals != wantWithdrawals || hasWithdrawalsRoot != wantWithdrawals {
|
||||
t.Fatalf("unexpected withdrawals fields: withdrawals=%v withdrawalsRoot=%v want=%v\n%s", hasWithdrawals, hasWithdrawalsRoot, wantWithdrawals, enc)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("pre-shanghai", func(t *testing.T) {
|
||||
// TestChainConfig has ShanghaiTime=nil, so all simulated blocks are pre-Shanghai.
|
||||
run(t, params.TestChainConfig, nil, false)
|
||||
})
|
||||
t.Run("post-shanghai", func(t *testing.T) {
|
||||
// MergedTestChainConfig has every fork active from genesis.
|
||||
run(t, params.MergedTestChainConfig, nil, true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSignTransaction(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Initialize test accounts
|
||||
|
|
|
|||
|
|
@ -318,12 +318,9 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
if precompiles != nil {
|
||||
evm.SetPrecompiles(precompiles)
|
||||
}
|
||||
if sim.chainConfig.IsPrague(header.Number, header.Time) || sim.chainConfig.IsUBT(header.Number, header.Time) {
|
||||
core.ProcessParentBlockHash(header.ParentHash, evm)
|
||||
}
|
||||
if header.ParentBeaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, evm)
|
||||
}
|
||||
// Run pre-execution system calls
|
||||
core.PreExecution(ctx, header.ParentBeaconRoot, header.ParentHash, sim.chainConfig, evm, header.Number, header.Time)
|
||||
|
||||
var allLogs []*types.Log
|
||||
for i, call := range block.Calls {
|
||||
// Terminate if the context is cancelled
|
||||
|
|
@ -393,22 +390,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
header.BlobGasUsed = &blobGasUsed
|
||||
}
|
||||
|
||||
// Process EIP-7685 requests
|
||||
var requests [][]byte
|
||||
if sim.chainConfig.IsPrague(header.Number, header.Time) {
|
||||
requests = [][]byte{}
|
||||
// EIP-6110
|
||||
if err := core.ParseDepositLogs(&requests, allLogs, sim.chainConfig); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// EIP-7002
|
||||
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// EIP-7251
|
||||
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
// Run post-execution system calls
|
||||
requests, err := core.PostExecution(ctx, sim.chainConfig, header.Number, header.Time, allLogs, evm)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if requests != nil {
|
||||
reqHash := types.CalcRequestsHash(requests)
|
||||
|
|
@ -417,7 +402,12 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
|
||||
blockBody := &types.Body{
|
||||
Transactions: txes,
|
||||
Withdrawals: *block.BlockOverrides.Withdrawals, // Withdrawal is also sanitized as non-nil
|
||||
}
|
||||
// Withdrawals are a post-Shanghai field. Attaching a non-nil withdrawals
|
||||
// slice would cause types.NewBlock to populate WithdrawalsHash on the
|
||||
// header and emit withdrawals fields for pre-Shanghai blocks.
|
||||
if sim.chainConfig.IsShanghai(header.Number, header.Time) {
|
||||
blockBody.Withdrawals = *block.BlockOverrides.Withdrawals
|
||||
}
|
||||
chainHeadReader := &simChainHeadReader{ctx, sim.b}
|
||||
|
||||
|
|
|
|||
|
|
@ -446,27 +446,27 @@ func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int,
|
|||
// Assumes that fields are not nil, i.e. setDefaults or CallDefaults has been called.
|
||||
func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck bool) *core.Message {
|
||||
var (
|
||||
gasPrice *big.Int
|
||||
gasFeeCap *big.Int
|
||||
gasTipCap *big.Int
|
||||
gasPrice *uint256.Int
|
||||
gasFeeCap *uint256.Int
|
||||
gasTipCap *uint256.Int
|
||||
)
|
||||
if baseFee == nil {
|
||||
gasPrice = args.GasPrice.ToInt()
|
||||
gasPrice, _ = args.GasPrice.ToUint256()
|
||||
gasFeeCap, gasTipCap = gasPrice, gasPrice
|
||||
} else {
|
||||
// A basefee is provided, necessitating 1559-type execution
|
||||
if args.GasPrice != nil {
|
||||
// User specified the legacy gas field, convert to 1559 gas typing
|
||||
gasPrice = args.GasPrice.ToInt()
|
||||
gasPrice, _ = args.GasPrice.ToUint256()
|
||||
gasFeeCap, gasTipCap = gasPrice, gasPrice
|
||||
} else {
|
||||
// User specified 1559 gas fields (or none), use those
|
||||
gasFeeCap = args.MaxFeePerGas.ToInt()
|
||||
gasTipCap = args.MaxPriorityFeePerGas.ToInt()
|
||||
gasFeeCap, _ = args.MaxFeePerGas.ToUint256()
|
||||
gasTipCap, _ = args.MaxPriorityFeePerGas.ToUint256()
|
||||
// Backfill the legacy gasPrice for EVM execution, unless we're all zeroes
|
||||
gasPrice = new(big.Int)
|
||||
gasPrice = uint256.NewInt(0)
|
||||
if gasFeeCap.BitLen() > 0 || gasTipCap.BitLen() > 0 {
|
||||
gasPrice = gasPrice.Add(gasTipCap, baseFee)
|
||||
gasPrice = gasPrice.Add(gasTipCap, uint256.MustFromBig(baseFee))
|
||||
if gasPrice.Cmp(gasFeeCap) > 0 {
|
||||
gasPrice = gasFeeCap
|
||||
}
|
||||
|
|
@ -477,10 +477,12 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck bool) *c
|
|||
if args.AccessList != nil {
|
||||
accessList = *args.AccessList
|
||||
}
|
||||
value, _ := args.Value.ToUint256()
|
||||
blobFeeCap, _ := args.BlobFeeCap.ToUint256()
|
||||
return &core.Message{
|
||||
From: args.from(),
|
||||
To: args.To,
|
||||
Value: (*big.Int)(args.Value),
|
||||
Value: value,
|
||||
Nonce: uint64(*args.Nonce),
|
||||
GasLimit: uint64(*args.Gas),
|
||||
GasPrice: gasPrice,
|
||||
|
|
@ -488,7 +490,7 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck bool) *c
|
|||
GasTipCap: gasTipCap,
|
||||
Data: args.data(),
|
||||
AccessList: accessList,
|
||||
BlobGasFeeCap: (*big.Int)(args.BlobFeeCap),
|
||||
BlobGasFeeCap: blobFeeCap,
|
||||
BlobHashes: args.BlobHashes,
|
||||
SetCodeAuthorizations: args.AuthorizationList,
|
||||
SkipNonceChecks: skipNonceCheck,
|
||||
|
|
|
|||
|
|
@ -208,21 +208,9 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams,
|
|||
}
|
||||
|
||||
// Collect consensus-layer requests if Prague is enabled.
|
||||
var requests [][]byte
|
||||
if miner.chainConfig.IsPrague(work.header.Number, work.header.Time) {
|
||||
requests = [][]byte{}
|
||||
// EIP-6110 deposits
|
||||
if err := core.ParseDepositLogs(&requests, allLogs, miner.chainConfig); err != nil {
|
||||
return &newPayloadResult{err: err}
|
||||
}
|
||||
// EIP-7002
|
||||
if err := core.ProcessWithdrawalQueue(&requests, work.evm); err != nil {
|
||||
return &newPayloadResult{err: err}
|
||||
}
|
||||
// EIP-7251 consolidations
|
||||
if err := core.ProcessConsolidationQueue(&requests, work.evm); err != nil {
|
||||
return &newPayloadResult{err: err}
|
||||
}
|
||||
requests, err := core.PostExecution(ctx, miner.chainConfig, work.header.Number, work.header.Time, allLogs, work.evm)
|
||||
if err != nil {
|
||||
return &newPayloadResult{err: err}
|
||||
}
|
||||
if requests != nil {
|
||||
reqHash := types.CalcRequestsHash(requests)
|
||||
|
|
@ -329,12 +317,8 @@ func (miner *Miner) prepareWork(ctx context.Context, genParams *generateParams,
|
|||
log.Error("Failed to create sealing context", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
if header.ParentBeaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, env.evm)
|
||||
}
|
||||
if miner.chainConfig.IsPrague(header.Number, header.Time) {
|
||||
core.ProcessParentBlockHash(header.ParentHash, env.evm)
|
||||
}
|
||||
// Run pre-execution system calls
|
||||
core.PreExecution(ctx, header.ParentBeaconRoot, header.ParentHash, miner.chainConfig, env.evm, header.Number, header.Time)
|
||||
return env, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
|
|
@ -44,7 +43,6 @@ import (
|
|||
|
||||
// Node is a container on which services can be registered.
|
||||
type Node struct {
|
||||
eventmux *event.TypeMux
|
||||
config *Config
|
||||
accman *accounts.Manager
|
||||
log log.Logger
|
||||
|
|
@ -108,7 +106,6 @@ func New(conf *Config) (*Node, error) {
|
|||
node := &Node{
|
||||
config: conf,
|
||||
inprocHandler: server,
|
||||
eventmux: new(event.TypeMux),
|
||||
log: conf.Logger,
|
||||
stop: make(chan struct{}),
|
||||
server: &p2p.Server{Config: conf.P2P},
|
||||
|
|
@ -692,12 +689,6 @@ func (n *Node) WSAuthEndpoint() string {
|
|||
return "ws://" + n.wsAuth.listenAddr() + n.wsAuth.wsConfig.prefix
|
||||
}
|
||||
|
||||
// EventMux retrieves the event multiplexer used by all the network services in
|
||||
// the current protocol stack.
|
||||
func (n *Node) EventMux() *event.TypeMux {
|
||||
return n.eventmux
|
||||
}
|
||||
|
||||
// OpenDatabaseWithOptions opens an existing database with the given name (or creates one if no
|
||||
// previous can be found) from within the node's instance directory. If the node has no
|
||||
// data directory, an in-memory database is returned.
|
||||
|
|
|
|||
|
|
@ -67,7 +67,10 @@ type tcpDialer struct {
|
|||
}
|
||||
|
||||
func (t tcpDialer) Dial(ctx context.Context, dest *enode.Node) (net.Conn, error) {
|
||||
addr, _ := dest.TCPEndpoint()
|
||||
addr, ok := dest.TCPEndpoint()
|
||||
if !ok {
|
||||
return nil, errNoPort
|
||||
}
|
||||
return t.d.DialContext(ctx, "tcp", addr.String())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -753,6 +753,41 @@ func (tab *Table) deleteNode(n *enode.Node) {
|
|||
|
||||
// waitForNodes blocks until the table contains at least n nodes.
|
||||
func (tab *Table) waitForNodes(ctx context.Context, n int) error {
|
||||
// Wrap ctx so the forwarder goroutine exits when waitForNodes returns,
|
||||
// regardless of whether the caller's ctx is canceled.
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// Set up a notification channel that gets unblocked when there was any activity on
|
||||
// the table. Ultimately this reads from the table's nodeFeed, but can't use the feed
|
||||
// directly on the same goroutine that takes Table.mutex, it would deadlock.
|
||||
var notify chan struct{}
|
||||
var notifyErr error
|
||||
initsub := func() event.Subscription {
|
||||
notify = make(chan struct{}, 1)
|
||||
newnode := make(chan *enode.Node, 1)
|
||||
sub := tab.nodeFeed.Subscribe(newnode)
|
||||
go func() {
|
||||
defer close(notify)
|
||||
for {
|
||||
select {
|
||||
case <-newnode:
|
||||
select {
|
||||
case notify <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
case <-ctx.Done():
|
||||
notifyErr = ctx.Err()
|
||||
return
|
||||
case <-tab.closeReq:
|
||||
notifyErr = errClosed
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return sub
|
||||
}
|
||||
|
||||
getlength := func() (count int) {
|
||||
for _, b := range &tab.buckets {
|
||||
count += len(b.entries)
|
||||
|
|
@ -760,28 +795,24 @@ func (tab *Table) waitForNodes(ctx context.Context, n int) error {
|
|||
return count
|
||||
}
|
||||
|
||||
var ch chan *enode.Node
|
||||
for {
|
||||
tab.mutex.Lock()
|
||||
if getlength() >= n {
|
||||
tab.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
if ch == nil {
|
||||
// Init subscription.
|
||||
ch = make(chan *enode.Node)
|
||||
sub := tab.nodeFeed.Subscribe(ch)
|
||||
if notify == nil {
|
||||
// Lazily init the subscription. Do this while holding the
|
||||
// lock so we don't miss any events that change the node count.
|
||||
sub := initsub()
|
||||
defer sub.Unsubscribe()
|
||||
}
|
||||
tab.mutex.Unlock()
|
||||
|
||||
// Wait for a node add event.
|
||||
select {
|
||||
case <-ch:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-tab.closeReq:
|
||||
return errClosed
|
||||
// Wait for table event.
|
||||
if _, ok := <-notify; !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
return notifyErr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package discover
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
|
@ -550,6 +551,45 @@ func TestSetFallbackNodes_DNSHostname(t *testing.T) {
|
|||
t.Logf("resolved localhost to %v", resolved.IPAddr())
|
||||
}
|
||||
|
||||
// This test checks that waitForNodes does not block addFoundNode.
|
||||
// See https://github.com/ethereum/go-ethereum/issues/34881.
|
||||
func TestTable_waitForNodesLocking(t *testing.T) {
|
||||
transport := newPingRecorder()
|
||||
tab, db := newTestTable(transport, Config{})
|
||||
defer db.Close()
|
||||
defer tab.close()
|
||||
<-tab.initDone
|
||||
|
||||
// waitForNodes will never reach this count, so it stays subscribed
|
||||
// to nodeFeed and looping for the duration of the test.
|
||||
waitCtx, cancelWait := context.WithCancel(context.Background())
|
||||
defer cancelWait()
|
||||
waitDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(waitDone)
|
||||
tab.waitForNodes(waitCtx, 1<<20)
|
||||
}()
|
||||
|
||||
// Call addFoundNode in loop to send to the feed.
|
||||
addDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(addDone)
|
||||
for i := range 10000 {
|
||||
d := 240 + (i % 17)
|
||||
n := nodeAtDistance(tab.self().ID(), d, intIP(i))
|
||||
tab.addFoundNode(n, true)
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-addDone:
|
||||
cancelWait()
|
||||
<-waitDone
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("deadlock detected: add loop did not finish within 10s")
|
||||
}
|
||||
}
|
||||
|
||||
func newkey() *ecdsa.PrivateKey {
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -447,6 +447,7 @@ func (t *UDPv4) loop() {
|
|||
// Start the timer so it fires when the next pending reply has expired.
|
||||
now := time.Now()
|
||||
for p, el := range iterList[*replyMatcher](plist) {
|
||||
nextTimeout = p
|
||||
if dist := p.deadline.Sub(now); dist < 2*respTimeout {
|
||||
timeout.Reset(dist)
|
||||
return
|
||||
|
|
@ -454,7 +455,7 @@ func (t *UDPv4) loop() {
|
|||
// Remove pending replies whose deadline is too far in the
|
||||
// future. These can occur if the system clock jumped
|
||||
// backwards after the deadline was assigned.
|
||||
nextTimeout.errc <- errClockWarp
|
||||
p.errc <- errClockWarp
|
||||
plist.Remove(el)
|
||||
}
|
||||
nextTimeout = nil
|
||||
|
|
@ -554,8 +555,9 @@ func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) {
|
|||
if err := t.handlePacket(from, buf[:nbytes]); err != nil && unhandled == nil {
|
||||
t.log.Debug("Bad discv4 packet", "addr", from, "err", err)
|
||||
} else if err != nil && unhandled != nil {
|
||||
p := ReadPacket{bytes.Clone(buf[:nbytes]), from}
|
||||
select {
|
||||
case unhandled <- ReadPacket{buf[:nbytes], from}:
|
||||
case unhandled <- p:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
|
@ -309,7 +310,8 @@ func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hex
|
|||
if sig[64] != 27 && sig[64] != 28 {
|
||||
return common.Address{}, errors.New("invalid Ethereum signature (V is not 27 or 28)")
|
||||
}
|
||||
sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
|
||||
sig = bytes.Clone(sig) // Avoid mutating the input
|
||||
sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
|
||||
hash := accounts.TextHash(data)
|
||||
rpk, err := crypto.SigToPub(hash, sig)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
func initMatcher(st *testMatcher) {
|
||||
|
|
@ -329,7 +328,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
|
|||
initialGas := vm.NewGasBudget(msg.GasLimit)
|
||||
|
||||
// Execute the message.
|
||||
_, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas.Copy(), uint256.MustFromBig(msg.Value))
|
||||
_, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas.Copy(), msg.Value)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -479,15 +479,15 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess
|
|||
From: from,
|
||||
To: to,
|
||||
Nonce: tx.Nonce,
|
||||
Value: value,
|
||||
Value: uint256.MustFromBig(value),
|
||||
GasLimit: gasLimit,
|
||||
GasPrice: gasPrice,
|
||||
GasFeeCap: tx.MaxFeePerGas,
|
||||
GasTipCap: tx.MaxPriorityFeePerGas,
|
||||
GasPrice: uint256.MustFromBig(gasPrice),
|
||||
GasFeeCap: uint256.MustFromBig(tx.MaxFeePerGas),
|
||||
GasTipCap: uint256.MustFromBig(tx.MaxPriorityFeePerGas),
|
||||
Data: data,
|
||||
AccessList: accessList,
|
||||
BlobHashes: tx.BlobVersionedHashes,
|
||||
BlobGasFeeCap: tx.BlobGasFeeCap,
|
||||
BlobGasFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap),
|
||||
SetCodeAuthorizations: authList,
|
||||
}
|
||||
return msg, nil
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ func (tt *TransactionTest) Run() error {
|
|||
return
|
||||
}
|
||||
// Intrinsic gas
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -92,7 +92,7 @@ func (tt *TransactionTest) Run() error {
|
|||
|
||||
if rules.IsPrague {
|
||||
var floorDataGas uint64
|
||||
floorDataGas, err = core.FloorDataGas(rules, tx.Data())
|
||||
floorDataGas, err = core.FloorDataGas(rules, tx.Data(), tx.AccessList())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,19 @@ const (
|
|||
NodeTypeBytes = 1 // Size of node type prefix in serialization
|
||||
HashSize = 32 // Size of a hash in bytes
|
||||
StemBitmapSize = 32 // Size of the bitmap in a stem node (256 values = 32 bytes)
|
||||
|
||||
MaxGroupDepth = 8
|
||||
)
|
||||
|
||||
// bitmapSizeForDepth returns the bitmap size in bytes for a given group depth.
|
||||
// For depths 1-3, returns 1 byte. For depths 4-8, returns 2^(depth-3) bytes.
|
||||
func bitmapSizeForDepth(groupDepth int) int {
|
||||
if groupDepth <= 3 {
|
||||
return 1
|
||||
}
|
||||
return 1 << (groupDepth - 3)
|
||||
}
|
||||
|
||||
const (
|
||||
nodeTypeStem = iota + 1
|
||||
nodeTypeInternal
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// TestSerializeDeserializeInternalNode tests flat 65-byte serialization and
|
||||
// deserialization of InternalNode through nodeStore.
|
||||
// TestSerializeDeserializeInternalNode tests grouped serialization and
|
||||
// deserialization of InternalNode through nodeStore at groupDepth=1.
|
||||
func TestSerializeDeserializeInternalNode(t *testing.T) {
|
||||
leftHash := common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
|
||||
rightHash := common.HexToHash("0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321")
|
||||
|
|
@ -39,24 +39,32 @@ func TestSerializeDeserializeInternalNode(t *testing.T) {
|
|||
rootNode.right = rightRef
|
||||
s.root = rootRef
|
||||
|
||||
// Serialize the node — flat 65-byte format
|
||||
serialized := s.serializeNode(rootRef)
|
||||
// Serialize the node — grouped format at groupDepth=1:
|
||||
// [type(1)][groupDepth(1)][bitmap(1)][leftHash(32)][rightHash(32)] = 67 bytes
|
||||
serialized := s.serializeNode(rootRef, 1)
|
||||
|
||||
// Check the serialized format: [type(1)][leftHash(32)][rightHash(32)]
|
||||
if serialized[0] != nodeTypeInternal {
|
||||
t.Errorf("Expected type byte to be %d, got %d", nodeTypeInternal, serialized[0])
|
||||
}
|
||||
if serialized[1] != 1 {
|
||||
t.Errorf("Expected groupDepth byte to be 1, got %d", serialized[1])
|
||||
}
|
||||
|
||||
expectedLen := NodeTypeBytes + 2*HashSize // 1 + 64 = 65
|
||||
expectedLen := NodeTypeBytes + 1 + 1 + 2*HashSize // type + groupDepth + bitmap + 2 hashes = 67
|
||||
if len(serialized) != expectedLen {
|
||||
t.Errorf("Expected serialized length to be %d, got %d", expectedLen, len(serialized))
|
||||
}
|
||||
|
||||
// Check that left and right hashes are embedded directly
|
||||
if !bytes.Equal(serialized[NodeTypeBytes:NodeTypeBytes+HashSize], leftHash[:]) {
|
||||
// Both children present at a 1-level group → bitmap byte = 0b11000000.
|
||||
if serialized[2] != 0xc0 {
|
||||
t.Errorf("Expected bitmap byte 0xc0, got 0x%02x", serialized[2])
|
||||
}
|
||||
|
||||
hashesStart := NodeTypeBytes + 1 + 1
|
||||
if !bytes.Equal(serialized[hashesStart:hashesStart+HashSize], leftHash[:]) {
|
||||
t.Error("Left hash not found at expected position")
|
||||
}
|
||||
if !bytes.Equal(serialized[NodeTypeBytes+HashSize:], rightHash[:]) {
|
||||
if !bytes.Equal(serialized[hashesStart+HashSize:], rightHash[:]) {
|
||||
t.Error("Right hash not found at expected position")
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +124,7 @@ func TestSerializeDeserializeStemNode(t *testing.T) {
|
|||
}
|
||||
|
||||
// Serialize the node
|
||||
serialized := s.serializeNode(ref)
|
||||
serialized := s.serializeNode(ref, 8)
|
||||
|
||||
// Check the serialized format
|
||||
if serialized[0] != nodeTypeStem {
|
||||
|
|
@ -195,8 +203,9 @@ func TestDeserializeInvalidType(t *testing.T) {
|
|||
// TestDeserializeInvalidLength tests deserialization with invalid data length.
|
||||
func TestDeserializeInvalidLength(t *testing.T) {
|
||||
s := newNodeStore()
|
||||
// InternalNode with valid type byte but wrong length (needs exactly 65 bytes)
|
||||
invalidData := []byte{nodeTypeInternal, 0, 0, 0}
|
||||
// InternalNode group header with groupDepth=1 (valid) and a 1-byte bitmap
|
||||
// announcing two present hashes, but the hash payload is missing.
|
||||
invalidData := []byte{nodeTypeInternal, 1, 0xc0}
|
||||
|
||||
_, err := s.deserializeNode(invalidData, 0)
|
||||
if err == nil {
|
||||
|
|
@ -208,6 +217,21 @@ func TestDeserializeInvalidLength(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestDeserializeInvalidGroupDepth tests deserialization when the group depth
|
||||
// byte is out of the supported 1..MaxGroupDepth range.
|
||||
func TestDeserializeInvalidGroupDepth(t *testing.T) {
|
||||
s := newNodeStore()
|
||||
invalidData := []byte{nodeTypeInternal, 0, 0, 0}
|
||||
|
||||
_, err := s.deserializeNode(invalidData, 0)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for invalid group depth, got nil")
|
||||
}
|
||||
if err.Error() != "invalid group depth" {
|
||||
t.Errorf("Expected 'invalid group depth' error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestKeyToPath tests the keyToPath function.
|
||||
func TestKeyToPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ func TestHashedNodeInsertValuesAtStem(t *testing.T) {
|
|||
sn.setValue(byte(i), v)
|
||||
}
|
||||
}
|
||||
serialized := rs.serializeNode(ref)
|
||||
serialized := rs.serializeNode(ref, 8)
|
||||
|
||||
validResolver := func(path []byte, hash common.Hash) ([]byte, error) {
|
||||
return serialized, nil
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ func TestInternalNodeGetWithResolver(t *testing.T) {
|
|||
ref := rs.newStemRef(stem, 1)
|
||||
sn := rs.getStem(ref.Index())
|
||||
sn.setValue(5, common.HexToHash("0xabcd").Bytes())
|
||||
return rs.serializeNode(ref), nil
|
||||
return rs.serializeNode(ref, 8), nil
|
||||
}
|
||||
return nil, errors.New("node not found")
|
||||
}
|
||||
|
|
@ -290,10 +290,7 @@ func TestInternalNodeCollectNodes(t *testing.T) {
|
|||
collectedPaths = append(collectedPaths, pathCopy)
|
||||
}
|
||||
|
||||
err := s.collectNodes(s.root, []byte{1}, flushFn)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to collect nodes: %v", err)
|
||||
}
|
||||
s.collectNodes(s.root, []byte{1}, flushFn, 8)
|
||||
|
||||
// Should have collected 3 nodes: left stem, right stem, and the internal node itself
|
||||
if len(collectedPaths) != 3 {
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ func (it *binaryNodeIterator) Path() []byte {
|
|||
}
|
||||
|
||||
func (it *binaryNodeIterator) NodeBlob() []byte {
|
||||
return it.store.serializeNode(it.current)
|
||||
return it.store.serializeNode(it.current, it.trie.groupDepth)
|
||||
}
|
||||
|
||||
// Leaf reports whether the iterator is currently positioned at a leaf value.
|
||||
|
|
|
|||
|
|
@ -320,10 +320,7 @@ func TestStemNodeCollectNodes(t *testing.T) {
|
|||
collectedPaths = append(collectedPaths, pathCopy)
|
||||
}
|
||||
|
||||
err := s.collectNodes(s.root, []byte{0, 1, 0}, flushFn)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to collect nodes: %v", err)
|
||||
}
|
||||
s.collectNodes(s.root, []byte{0, 1, 0}, flushFn, 8)
|
||||
|
||||
// Should have collected one node (itself)
|
||||
if len(collectedPaths) != 1 {
|
||||
|
|
|
|||
|
|
@ -107,18 +107,83 @@ func (s *nodeStore) hashInternal(idx uint32) common.Hash {
|
|||
return node.hash
|
||||
}
|
||||
|
||||
// SerializeNode serializes a node into the flat on-disk format.
|
||||
func (s *nodeStore) serializeNode(ref nodeRef) []byte {
|
||||
// serializeSubtree recursively collects child hashes from a subtree of InternalNodes.
|
||||
// It traverses up to `remainingDepth` levels, storing hashes of bottom-layer children.
|
||||
// position tracks the current index (0 to 2^groupDepth - 1) for bitmap placement.
|
||||
// hashes collects the hashes of present children, bitmap tracks which positions are present.
|
||||
func (s *nodeStore) serializeSubtree(ref nodeRef, remainingDepth int, position int, absoluteDepth int, bitmap []byte, hashes *[]common.Hash) {
|
||||
if remainingDepth == 0 {
|
||||
// Bottom layer: store hash if not empty
|
||||
switch ref.Kind() {
|
||||
case kindEmpty:
|
||||
// Leave bitmap bit unset, don't add hash
|
||||
return
|
||||
default:
|
||||
// StemNode, HashedNode, or InternalNode at boundary: store hash
|
||||
bitmap[position/8] |= 1 << (7 - (position % 8))
|
||||
*hashes = append(*hashes, s.computeHash(ref))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch ref.Kind() {
|
||||
case kindInternal:
|
||||
leftPos := position * 2
|
||||
rightPos := position*2 + 1
|
||||
s.serializeSubtree(s.getInternal(ref.Index()).left, remainingDepth-1, leftPos, absoluteDepth+1, bitmap, hashes)
|
||||
s.serializeSubtree(s.getInternal(ref.Index()).right, remainingDepth-1, rightPos, absoluteDepth+1, bitmap, hashes)
|
||||
case kindEmpty:
|
||||
return
|
||||
default:
|
||||
// StemNode or HashedNode encountered before reaching the group's bottom
|
||||
// layer. Compute the leaf bitmap position where this node's hash will
|
||||
// be stored.
|
||||
leafPos := position
|
||||
switch ref.Kind() {
|
||||
case kindStem:
|
||||
sn := s.getStem(ref.Index())
|
||||
// Extend position using the stem's key bits so that
|
||||
// GetValuesAtStem traversal (which follows key bits) finds the hash.
|
||||
for d := 0; d < remainingDepth; d++ {
|
||||
bit := sn.Stem[(absoluteDepth+d)/8] >> (7 - ((absoluteDepth + d) % 8)) & 1
|
||||
leafPos = leafPos*2 + int(bit)
|
||||
}
|
||||
default:
|
||||
// HashedNode or unknown: extend all-left (no key bits available).
|
||||
// This matches the all-zero path that resolveNode would follow.
|
||||
leafPos = position << remainingDepth
|
||||
}
|
||||
bitmap[leafPos/8] |= 1 << (7 - (leafPos % 8))
|
||||
*hashes = append(*hashes, s.computeHash(ref))
|
||||
}
|
||||
}
|
||||
|
||||
// SerializeNode serializes a node into the flat on-disk format.
|
||||
func (s *nodeStore) serializeNode(ref nodeRef, groupDepth int) []byte {
|
||||
switch ref.Kind() {
|
||||
case kindInternal:
|
||||
// InternalNode group: 1 byte type + 1 byte group depth + variable bitmap + N×32 byte hashes
|
||||
bitmapSize := bitmapSizeForDepth(groupDepth)
|
||||
bitmap := make([]byte, bitmapSize)
|
||||
var hashes []common.Hash
|
||||
|
||||
node := s.getInternal(ref.Index())
|
||||
var serialized [NodeTypeBytes + HashSize + HashSize]byte
|
||||
s.serializeSubtree(ref, groupDepth, 0, int(node.depth), bitmap, &hashes)
|
||||
|
||||
// Build serialized output
|
||||
serializedLen := NodeTypeBytes + 1 + bitmapSize + len(hashes)*HashSize
|
||||
serialized := make([]byte, serializedLen)
|
||||
serialized[0] = nodeTypeInternal
|
||||
lh := s.computeHash(node.left)
|
||||
rh := s.computeHash(node.right)
|
||||
copy(serialized[NodeTypeBytes:NodeTypeBytes+HashSize], lh[:])
|
||||
copy(serialized[NodeTypeBytes+HashSize:], rh[:])
|
||||
return serialized[:]
|
||||
serialized[1] = byte(groupDepth) // group depth => bitmap size for a sparse group
|
||||
copy(serialized[2:2+bitmapSize], bitmap)
|
||||
|
||||
offset := NodeTypeBytes + 1 + bitmapSize
|
||||
for _, h := range hashes {
|
||||
copy(serialized[offset:offset+HashSize], h.Bytes())
|
||||
offset += HashSize
|
||||
}
|
||||
|
||||
return serialized
|
||||
|
||||
case kindStem:
|
||||
sn := s.getStem(ref.Index())
|
||||
|
|
@ -163,6 +228,59 @@ func (s *nodeStore) deserializeNodeWithHash(serialized []byte, depth int, hn com
|
|||
return s.decodeNode(serialized, depth, hn, false, false)
|
||||
}
|
||||
|
||||
// deserializeSubtree reconstructs an InternalNode subtree from grouped serialization.
|
||||
// remainingDepth is how many more levels to build, position is current index in the bitmap,
|
||||
// nodeDepth is the actual trie depth for the node being created.
|
||||
// hashIdx tracks the current position in the hash data (incremented as hashes are consumed).
|
||||
func (s *nodeStore) deserializeSubtree(hn common.Hash, remainingDepth int, position int, nodeDepth int, bitmap []byte, hashData []byte, hashIdx *int, mustRecompute bool, dirty bool) (nodeRef, error) {
|
||||
if remainingDepth == 0 {
|
||||
// Bottom layer: check bitmap and return HashedNode or Empty
|
||||
if bitmap[position/8]>>(7-(position%8))&1 == 1 {
|
||||
if len(hashData) < (*hashIdx+1)*HashSize {
|
||||
return emptyRef, errInvalidSerializedLength
|
||||
}
|
||||
hash := common.BytesToHash(hashData[*hashIdx*HashSize : (*hashIdx+1)*HashSize])
|
||||
*hashIdx++
|
||||
return s.newHashedRef(hash), nil
|
||||
}
|
||||
return emptyRef, nil
|
||||
}
|
||||
|
||||
// Check if this entire subtree is empty by examining all relevant bitmap bits
|
||||
leftPos := position * 2
|
||||
rightPos := position*2 + 1
|
||||
|
||||
// note that the parent might not need root computations, but the children
|
||||
// do, because their hash isn't saved. Hence `mustRecompute` is set to `true`.
|
||||
left, err := s.deserializeSubtree(common.Hash{}, remainingDepth-1, leftPos, nodeDepth+1, bitmap, hashData, hashIdx, true, dirty)
|
||||
if err != nil {
|
||||
return emptyRef, err
|
||||
}
|
||||
right, err := s.deserializeSubtree(common.Hash{}, remainingDepth-1, rightPos, nodeDepth+1, bitmap, hashData, hashIdx, true, dirty)
|
||||
if err != nil {
|
||||
return emptyRef, err
|
||||
}
|
||||
|
||||
// If both children are empty, return Empty
|
||||
if left.IsEmpty() && right.IsEmpty() {
|
||||
return emptyRef, nil
|
||||
}
|
||||
|
||||
ref := s.newInternalRef(nodeDepth)
|
||||
node := s.getInternal(ref.Index())
|
||||
node.left = left
|
||||
node.right = right
|
||||
node.mustRecompute = mustRecompute
|
||||
if !mustRecompute {
|
||||
// mustRecompute will only be false for the root of the subtree,
|
||||
// for which we already know the hash.
|
||||
node.hash = hn
|
||||
node.mustRecompute = false
|
||||
}
|
||||
node.dirty = dirty
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
func (s *nodeStore) decodeNode(serialized []byte, depth int, hn common.Hash, mustRecompute, dirty bool) (nodeRef, error) {
|
||||
if len(serialized) == 0 {
|
||||
return emptyRef, nil
|
||||
|
|
@ -170,31 +288,23 @@ func (s *nodeStore) decodeNode(serialized []byte, depth int, hn common.Hash, mus
|
|||
|
||||
switch serialized[0] {
|
||||
case nodeTypeInternal:
|
||||
if len(serialized) != NodeTypeBytes+2*HashSize {
|
||||
// Grouped format: 1 byte type + 1 byte group depth + variable bitmap + N×32 byte hashes
|
||||
if len(serialized) < NodeTypeBytes+1 {
|
||||
return emptyRef, errInvalidSerializedLength
|
||||
}
|
||||
var leftHash, rightHash common.Hash
|
||||
copy(leftHash[:], serialized[NodeTypeBytes:NodeTypeBytes+HashSize])
|
||||
copy(rightHash[:], serialized[NodeTypeBytes+HashSize:])
|
||||
groupDepth := int(serialized[1])
|
||||
if groupDepth < 1 || groupDepth > MaxGroupDepth {
|
||||
return 0, errors.New("invalid group depth")
|
||||
}
|
||||
bitmapSize := bitmapSizeForDepth(groupDepth)
|
||||
if len(serialized) < NodeTypeBytes+1+bitmapSize {
|
||||
return 0, errInvalidSerializedLength
|
||||
}
|
||||
bitmap := serialized[2 : 2+bitmapSize]
|
||||
hashData := serialized[2+bitmapSize:]
|
||||
|
||||
var leftRef, rightRef nodeRef
|
||||
if leftHash != (common.Hash{}) {
|
||||
leftRef = s.newHashedRef(leftHash)
|
||||
}
|
||||
if rightHash != (common.Hash{}) {
|
||||
rightRef = s.newHashedRef(rightHash)
|
||||
}
|
||||
|
||||
ref := s.newInternalRef(depth)
|
||||
node := s.getInternal(ref.Index())
|
||||
node.left = leftRef
|
||||
node.right = rightRef
|
||||
if !mustRecompute {
|
||||
node.hash = hn
|
||||
node.mustRecompute = false
|
||||
}
|
||||
node.dirty = dirty
|
||||
return ref, nil
|
||||
hashIdx := 0
|
||||
return s.deserializeSubtree(hn, groupDepth, 0, depth, bitmap, hashData, &hashIdx, mustRecompute, dirty)
|
||||
|
||||
case nodeTypeStem:
|
||||
if len(serialized) < NodeTypeBytes+StemSize+StemBitmapSize {
|
||||
|
|
@ -230,45 +340,112 @@ func (s *nodeStore) decodeNode(serialized []byte, depth int, hn common.Hash, mus
|
|||
// CollectNodes flushes every node that needs flushing via flushfn in post-order.
|
||||
// Invariant: any ancestor of a node that needs flushing is itself marked, so a
|
||||
// clean root means the whole subtree is clean.
|
||||
func (s *nodeStore) collectNodes(ref nodeRef, path []byte, flushfn nodeFlushFn) error {
|
||||
func (s *nodeStore) collectNodes(ref nodeRef, path []byte, flushfn nodeFlushFn, groupDepth int) {
|
||||
switch ref.Kind() {
|
||||
case kindEmpty:
|
||||
return nil
|
||||
case kindInternal:
|
||||
node := s.getInternal(ref.Index())
|
||||
if !node.dirty {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
// Reuse path buffer across children: flushfn consumers
|
||||
// (NodeSet.AddNode, tracer.Get) clone via string(path), so in-place
|
||||
// mutation is safe.
|
||||
path = append(path, 0)
|
||||
if err := s.collectNodes(node.left, path, flushfn); err != nil {
|
||||
return err
|
||||
// Only flush at group boundaries (depth % groupDepth == 0)
|
||||
if int(node.depth)%groupDepth == 0 {
|
||||
// We're at a group boundary - first collect any nodes in deeper groups,
|
||||
// then flush this group
|
||||
s.collectChildGroups(node, path, flushfn, groupDepth, groupDepth-1)
|
||||
flushfn(path, s.computeHash(ref), s.serializeNode(ref, groupDepth))
|
||||
node.dirty = false
|
||||
return
|
||||
}
|
||||
path[len(path)-1] = 1
|
||||
if err := s.collectNodes(node.right, path, flushfn); err != nil {
|
||||
return err
|
||||
}
|
||||
path = path[:len(path)-1]
|
||||
flushfn(path, s.computeHash(ref), s.serializeNode(ref))
|
||||
node.dirty = false
|
||||
return nil
|
||||
// Not at a group boundary - this shouldn't happen if we're called correctly from root
|
||||
// but handle it by continuing to traverse
|
||||
s.collectChildGroups(node, path, flushfn, groupDepth, groupDepth-(int(node.depth)%groupDepth)-1)
|
||||
case kindStem:
|
||||
sn := s.getStem(ref.Index())
|
||||
if !sn.dirty {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
flushfn(path, s.computeHash(ref), s.serializeNode(ref))
|
||||
flushfn(path, s.computeHash(ref), s.serializeNode(ref, groupDepth))
|
||||
sn.dirty = false
|
||||
return nil
|
||||
case kindHashed:
|
||||
return nil // Already committed
|
||||
case kindHashed, kindEmpty:
|
||||
default:
|
||||
return fmt.Errorf("CollectNodes: unexpected kind %d", ref.Kind())
|
||||
panic(fmt.Sprintf("CollectNodes: unexpected kind %d", ref.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
// collectChildGroups traverses within a group to find and collect nodes in the next group.
|
||||
// remainingLevels is how many more levels below the current node until we reach the group boundary.
|
||||
// When remainingLevels=0, the current node's children are at the next group boundary.
|
||||
func (s *nodeStore) collectChildGroups(node *InternalNode, path []byte, flushfn nodeFlushFn, groupDepth int, remainingLevels int) error {
|
||||
if remainingLevels == 0 {
|
||||
// Current node is at depth (groupBoundary - 1), its children are at the next group boundary
|
||||
if !node.left.IsEmpty() {
|
||||
s.collectNodes(node.left, appendBit(path, 0), flushfn, groupDepth)
|
||||
}
|
||||
if !node.right.IsEmpty() {
|
||||
s.collectNodes(node.right, appendBit(path, 1), flushfn, groupDepth)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if !node.left.IsEmpty() {
|
||||
switch node.left.Kind() {
|
||||
case kindInternal:
|
||||
n := s.getInternal(node.left.Index())
|
||||
if err := s.collectChildGroups(n, appendBit(path, 0), flushfn, groupDepth, remainingLevels-1); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
extPath := s.extendPathToGroupLeaf(appendBit(path, 0), node.left, remainingLevels)
|
||||
s.collectNodes(node.left, extPath, flushfn, groupDepth)
|
||||
}
|
||||
}
|
||||
if !node.right.IsEmpty() {
|
||||
switch node.right.Kind() {
|
||||
case kindInternal:
|
||||
n := s.getInternal(node.right.Index())
|
||||
if err := s.collectChildGroups(n, appendBit(path, 1), flushfn, groupDepth, remainingLevels-1); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
extPath := s.extendPathToGroupLeaf(appendBit(path, 1), node.right, remainingLevels)
|
||||
s.collectNodes(node.right, extPath, flushfn, groupDepth)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// extendPathToGroupLeaf extends a storage path to the group's leaf boundary,
|
||||
// matching the projection done by serializeSubtree. For StemNodes, the path
|
||||
// is extended using the stem's key bits (same as serializeSubtree). For other
|
||||
// node types, the path is extended with all-zero (left) bits.
|
||||
func (s *nodeStore) extendPathToGroupLeaf(path []byte, node nodeRef, remainingLevels int) []byte {
|
||||
if remainingLevels <= 0 {
|
||||
return path
|
||||
}
|
||||
if node.Kind() == kindStem {
|
||||
sn := s.getStem(node.Index())
|
||||
for _ = range remainingLevels {
|
||||
bit := sn.Stem[len(path)/8] >> (7 - (len(path) % 8)) & 1
|
||||
path = appendBit(path, bit)
|
||||
}
|
||||
} else {
|
||||
// HashedNode or other: all-left extension (matches serializeSubtree's
|
||||
// position << remainingDepth behavior).
|
||||
for _ = range remainingLevels {
|
||||
path = appendBit(path, 0)
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// appendBit appends a bit to a path, returning a new slice
|
||||
func appendBit(path []byte, bit byte) []byte {
|
||||
var p [256]byte
|
||||
copy(p[:], path)
|
||||
result := p[:len(path)]
|
||||
return append(result, bit)
|
||||
}
|
||||
|
||||
func (s *nodeStore) toDot(ref nodeRef, parent, path string) string {
|
||||
switch ref.Kind() {
|
||||
case kindInternal:
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue