Merge branch 'master' into upgrade_hasher_pool

This commit is contained in:
maskpp 2025-05-21 20:50:02 +08:00
commit 35394489e5
105 changed files with 9110 additions and 1612 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -21,9 +21,12 @@ import (
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"runtime"
"slices"
"strconv"
"strings"
"sync/atomic"
"time"
@ -39,6 +42,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/era"
"github.com/ethereum/go-ethereum/internal/era/eradl"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
@ -190,7 +194,7 @@ This command dumps out the state for a given block (or latest, if none provided)
`,
}
pruneCommand = &cli.Command{
pruneHistoryCommand = &cli.Command{
Action: pruneHistory,
Name: "prune-history",
Usage: "Prune blockchain history (block bodies and receipts) up to the merge block",
@ -201,6 +205,42 @@ The prune-history command removes historical block bodies and receipts from the
blockchain database up to the merge block, while preserving block headers. This
helps reduce storage requirements for nodes that don't need full historical data.`,
}
downloadEraCommand = &cli.Command{
Action: downloadEra,
Name: "download-era",
Usage: "Fetches era1 files (pre-merge history) from an HTTP endpoint",
ArgsUsage: "",
Flags: slices.Concat(
utils.DatabaseFlags,
utils.NetworkFlags,
[]cli.Flag{
eraBlockFlag,
eraEpochFlag,
eraAllFlag,
eraServerFlag,
},
),
}
)
var (
eraBlockFlag = &cli.StringFlag{
Name: "block",
Usage: "Block number to fetch. (can also be a range <start>-<end>)",
}
eraEpochFlag = &cli.StringFlag{
Name: "epoch",
Usage: "Epoch number to fetch (can also be a range <start>-<end>)",
}
eraAllFlag = &cli.BoolFlag{
Name: "all",
Usage: "Download all available era1 files",
}
eraServerFlag = &cli.StringFlag{
Name: "server",
Usage: "era1 server URL",
}
)
// initGenesis will initialise the given JSON format genesis file and writes it as
@ -665,3 +705,83 @@ func pruneHistory(ctx *cli.Context) error {
return nil
}
// downladEra is the era1 file downloader tool.
func downloadEra(ctx *cli.Context) error {
flags.CheckExclusive(ctx, eraBlockFlag, eraEpochFlag, eraAllFlag)
// Resolve the network.
var network = "mainnet"
if utils.IsNetworkPreset(ctx) {
switch {
case ctx.IsSet(utils.MainnetFlag.Name):
case ctx.IsSet(utils.SepoliaFlag.Name):
network = "sepolia"
default:
return fmt.Errorf("unsupported network, no known era1 checksums")
}
}
// Resolve the destination directory.
stack, _ := makeConfigNode(ctx)
defer stack.Close()
ancients := stack.ResolveAncient("chaindata", "")
dir := filepath.Join(ancients, "era")
baseURL := ctx.String(eraServerFlag.Name)
if baseURL == "" {
return fmt.Errorf("need --%s flag to download", eraServerFlag.Name)
}
l, err := eradl.New(baseURL, network)
if err != nil {
return err
}
switch {
case ctx.IsSet(eraAllFlag.Name):
return l.DownloadAll(dir)
case ctx.IsSet(eraBlockFlag.Name):
s := ctx.String(eraBlockFlag.Name)
start, end, ok := parseRange(s)
if !ok {
return fmt.Errorf("invalid block range: %q", s)
}
return l.DownloadBlockRange(start, end, dir)
case ctx.IsSet(eraEpochFlag.Name):
s := ctx.String(eraEpochFlag.Name)
start, end, ok := parseRange(s)
if !ok {
return fmt.Errorf("invalid epoch range: %q", s)
}
return l.DownloadEpochRange(start, end, dir)
default:
return fmt.Errorf("specify one of --%s, --%s, or --%s to download", eraAllFlag.Name, eraBlockFlag.Name, eraEpochFlag.Name)
}
}
func parseRange(s string) (start uint64, end uint64, ok bool) {
if m, _ := regexp.MatchString("[0-9]+", s); m {
start, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, 0, false
}
end = start
return start, end, true
}
if m, _ := regexp.MatchString("[0-9]+-[0-9]+", s); m {
s1, s2, _ := strings.Cut(s, "-")
start, err := strconv.ParseUint(s1, 10, 64)
if err != nil {
return 0, 0, false
}
end, err = strconv.ParseUint(s2, 10, 64)
if err != nil {
return 0, 0, false
}
return start, end, true
}
return 0, 0, false
}

View file

@ -91,13 +91,7 @@ var (
utils.LogNoHistoryFlag,
utils.LogExportCheckpointsFlag,
utils.StateHistoryFlag,
utils.LightServeFlag, // deprecated
utils.LightIngressFlag, // deprecated
utils.LightEgressFlag, // deprecated
utils.LightMaxPeersFlag, // deprecated
utils.LightNoPruneFlag, // deprecated
utils.LightKDFFlag,
utils.LightNoSyncServeFlag, // deprecated
utils.EthRequiredBlocksFlag,
utils.LegacyWhitelistFlag, // deprecated
utils.CacheFlag,
@ -225,7 +219,8 @@ func init() {
removedbCommand,
dumpCommand,
dumpGenesisCommand,
pruneCommand,
pruneHistoryCommand,
downloadEraCommand,
// See accountcmd.go:
accountCommand,
walletCommand,

View file

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

View file

@ -1245,28 +1245,6 @@ func setIPC(ctx *cli.Context, cfg *node.Config) {
}
}
// setLes shows the deprecation warnings for LES flags.
func setLes(ctx *cli.Context, cfg *ethconfig.Config) {
if ctx.IsSet(LightServeFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightServeFlag.Name)
}
if ctx.IsSet(LightIngressFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightIngressFlag.Name)
}
if ctx.IsSet(LightEgressFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightEgressFlag.Name)
}
if ctx.IsSet(LightMaxPeersFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightMaxPeersFlag.Name)
}
if ctx.IsSet(LightNoPruneFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightNoPruneFlag.Name)
}
if ctx.IsSet(LightNoSyncServeFlag.Name) {
log.Warn("The light server has been deprecated, please remove this flag", "flag", LightNoSyncServeFlag.Name)
}
}
// MakeDatabaseHandles raises out the number of allowed file handles per process
// for Geth and returns half of the allowance to assign to the database.
func MakeDatabaseHandles(max int) int {
@ -1582,7 +1560,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
setBlobPool(ctx, &cfg.BlobPool)
setMiner(ctx, &cfg.Miner)
setRequiredBlocks(ctx, cfg)
setLes(ctx, cfg)
// Cap the cache allowance and tune the garbage collector
mem, err := gopsutil.VirtualMemory()
@ -1712,7 +1689,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
}
}
if ctx.IsSet(VMEnableDebugFlag.Name) {
// TODO(fjl): force-enable this in --dev mode
cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name)
}
@ -1761,6 +1737,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
case ctx.Bool(DeveloperFlag.Name):
cfg.NetworkId = 1337
cfg.SyncMode = ethconfig.FullSync
cfg.EnablePreimageRecording = true
// Create new developer account or reuse existing one
var (
developer accounts.Account
@ -2043,7 +2020,6 @@ func SetupMetrics(cfg *metrics.Config) {
log.Info("Enabling metrics export to InfluxDB")
go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "geth.", tagsMap)
} else if enableExportV2 {
tagsMap := SplitTagsFlag(cfg.InfluxDBTags)
log.Info("Enabling metrics export to InfluxDB (v2)")
go influxdb.InfluxDBV2WithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, token, bucket, organization, "geth.", tagsMap)
}

View file

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

View file

@ -183,8 +183,13 @@ func (c *CacheConfig) triedbConfig(isVerkle bool) *triedb.Config {
}
if c.StateScheme == rawdb.PathScheme {
config.PathDB = &pathdb.Config{
StateHistory: c.StateHistory,
CleanCacheSize: c.TrieCleanLimit * 1024 * 1024,
StateHistory: c.StateHistory,
TrieCleanSize: c.TrieCleanLimit * 1024 * 1024,
StateCleanSize: c.SnapshotLimit * 1024 * 1024,
// TODO(rjl493456442): The write buffer represents the memory limit used
// for flushing both trie data and state data to disk. The config name
// should be updated to eliminate the confusion.
WriteBufferSize: c.TrieDirtyLimit * 1024 * 1024,
}
}
@ -380,11 +385,14 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
// Do nothing here until the state syncer picks it up.
log.Info("Genesis state is missing, wait state sync")
} else {
// Head state is missing, before the state recovery, find out the
// disk layer point of snapshot(if it's enabled). Make sure the
// rewound point is lower than disk layer.
// Head state is missing, before the state recovery, find out the disk
// layer point of snapshot(if it's enabled). Make sure the rewound point
// is lower than disk layer.
//
// Note it's unnecessary in path mode which always keep trie data and
// state data consistent.
var diskRoot common.Hash
if bc.cacheConfig.SnapshotLimit > 0 {
if bc.cacheConfig.SnapshotLimit > 0 && bc.cacheConfig.StateScheme == rawdb.HashScheme {
diskRoot = rawdb.ReadSnapshotRoot(bc.db)
}
if diskRoot != (common.Hash{}) {
@ -457,7 +465,32 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
bc.logger.OnGenesisBlock(bc.genesisBlock, alloc)
}
}
bc.setupSnapshot()
// Rewind the chain in case of an incompatible config upgrade.
if compatErr != nil {
log.Warn("Rewinding chain to upgrade configuration", "err", compatErr)
if compatErr.RewindToTime > 0 {
bc.SetHeadWithTimestamp(compatErr.RewindToTime)
} else {
bc.SetHead(compatErr.RewindToBlock)
}
rawdb.WriteChainConfig(db, genesisHash, chainConfig)
}
// Start tx indexer if it's enabled.
if txLookupLimit != nil {
bc.txIndexer = newTxIndexer(*txLookupLimit, bc)
}
return bc, nil
}
func (bc *BlockChain) setupSnapshot() {
// Short circuit if the chain is established with path scheme, as the
// state snapshot has been integrated into path database natively.
if bc.cacheConfig.StateScheme == rawdb.PathScheme {
return
}
// Load any existing snapshot, regenerating it if loading failed
if bc.cacheConfig.SnapshotLimit > 0 {
// If the chain was rewound past the snapshot persistent layer (causing
@ -465,7 +498,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
// in recovery mode and in that case, don't invalidate the snapshot on a
// head mismatch.
var recover bool
head := bc.CurrentBlock()
if layer := rawdb.ReadSnapshotRecoveryNumber(bc.db); layer != nil && *layer >= head.Number.Uint64() {
log.Warn("Enabling snapshot recovery", "chainhead", head.Number, "diskbase", *layer)
@ -482,22 +514,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
// Re-initialize the state database with snapshot
bc.statedb = state.NewDatabase(bc.triedb, bc.snaps)
}
// Rewind the chain in case of an incompatible config upgrade.
if compatErr != nil {
log.Warn("Rewinding chain to upgrade configuration", "err", compatErr)
if compatErr.RewindToTime > 0 {
bc.SetHeadWithTimestamp(compatErr.RewindToTime)
} else {
bc.SetHead(compatErr.RewindToBlock)
}
rawdb.WriteChainConfig(db, genesisHash, chainConfig)
}
// Start tx indexer if it's enabled.
if txLookupLimit != nil {
bc.txIndexer = newTxIndexer(*txLookupLimit, bc)
}
return bc, nil
}
// empty returns an indicator whether the blockchain is empty.
@ -1293,12 +1309,11 @@ const (
//
// The optional ancientLimit can also be specified and chain segment before that
// will be directly stored in the ancient, getting rid of the chain migration.
func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts, ancientLimit uint64) (int, error) {
func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []rlp.RawValue, ancientLimit uint64) (int, error) {
// Verify the supplied headers before insertion without lock
var headers []*types.Header
for _, block := range blockChain {
headers = append(headers, block.Header())
// Here we also validate that blob transactions in the block do not
// contain a sidecar. While the sidecar does not affect the block hash
// or tx hash, sending blobs within a block is not allowed.
@ -1341,11 +1356,11 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
//
// this function only accepts canonical chain data. All side chain will be reverted
// eventually.
writeAncient := func(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) {
writeAncient := func(blockChain types.Blocks, receiptChain []rlp.RawValue) (int, error) {
// Ensure genesis is in the ancient store
if blockChain[0].NumberU64() == 1 {
if frozen, _ := bc.db.Ancients(); frozen == 0 {
writeSize, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []types.Receipts{nil})
writeSize, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []rlp.RawValue{rlp.EmptyList})
if err != nil {
log.Error("Error writing genesis to ancients", "err", err)
return 0, err
@ -1388,7 +1403,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
// existing local chain segments (reorg around the chain tip). The reorganized part
// will be included in the provided chain segment, and stale canonical markers will be
// silently rewritten. Therefore, no explicit reorg logic is needed.
writeLive := func(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) {
writeLive := func(blockChain types.Blocks, receiptChain []rlp.RawValue) (int, error) {
var (
skipPresenceCheck = false
batch = bc.db.NewBatch()
@ -1413,7 +1428,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
// Write all the data out into the database
rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64())
rawdb.WriteBlock(batch, block)
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receiptChain[i])
rawdb.WriteRawReceipts(batch, block.Hash(), block.NumberU64(), receiptChain[i])
// Write everything belongs to the blocks into the database. So that
// we can ensure all components of body is completed(body, receipts)
@ -2634,7 +2649,7 @@ func (bc *BlockChain) InsertHeadersBeforeCutoff(headers []*types.Header) (int, e
first = headers[0].Number.Uint64()
)
if first == 1 && frozen == 0 {
_, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []types.Receipts{nil})
_, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []rlp.RawValue{rlp.EmptyList})
if err != nil {
log.Error("Error writing genesis to ancients", "err", err)
return 0, err

View file

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

View file

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

View file

@ -2023,7 +2023,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
}
if tt.commitBlock > 0 {
chain.triedb.Commit(canonblocks[tt.commitBlock-1].Root(), false)
if snapshots {
if snapshots && scheme == rawdb.HashScheme {
if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil {
t.Fatalf("Failed to flatten snapshots: %v", err)
}

View file

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

View file

@ -734,7 +734,7 @@ func testFastVsFullChains(t *testing.T, scheme string) {
fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer fast.Stop()
if n, err := fast.InsertReceiptChain(blocks, receipts, 0); err != nil {
if n, err := fast.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err)
}
// Freezer style fast import the chain.
@ -747,7 +747,7 @@ func testFastVsFullChains(t *testing.T, scheme string) {
ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer ancient.Stop()
if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(len(blocks)/2)); err != nil {
if n, err := ancient.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), uint64(len(blocks)/2)); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err)
}
@ -871,7 +871,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer fast.Stop()
if n, err := fast.InsertReceiptChain(blocks, receipts, 0); err != nil {
if n, err := fast.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err)
}
assert(t, "fast", fast, height, height, 0)
@ -884,7 +884,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer ancient.Stop()
if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(3*len(blocks)/4)); err != nil {
if n, err := ancient.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), uint64(3*len(blocks)/4)); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err)
}
assert(t, "ancient", ancient, height, height, 0)
@ -1696,7 +1696,7 @@ func testBlockchainRecovery(t *testing.T, scheme string) {
defer ancientDb.Close()
ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(3*len(blocks)/4)); err != nil {
if n, err := ancient.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), uint64(3*len(blocks)/4)); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err)
}
rawdb.WriteLastPivotNumber(ancientDb, blocks[len(blocks)-1].NumberU64()) // Force fast sync behavior
@ -1991,7 +1991,7 @@ func testInsertKnownChainData(t *testing.T, typ string, scheme string) {
}
} else if typ == "receipts" {
inserter = func(blocks []*types.Block, receipts []types.Receipts) error {
_, err = chain.InsertReceiptChain(blocks, receipts, 0)
_, err = chain.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0)
return err
}
asserter = func(t *testing.T, block *types.Block) {
@ -2157,7 +2157,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i
}
} else if typ == "receipts" {
inserter = func(blocks []*types.Block, receipts []types.Receipts) error {
_, err = chain.InsertReceiptChain(blocks, receipts, 0)
_, err = chain.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0)
return err
}
asserter = func(t *testing.T, block *types.Block) {
@ -4205,10 +4205,10 @@ func testChainReorgSnapSync(t *testing.T, ancientLimit uint64) {
chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
defer chain.Stop()
if n, err := chain.InsertReceiptChain(blocks, receipts, ancientLimit); err != nil {
if n, err := chain.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), ancientLimit); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err)
}
if n, err := chain.InsertReceiptChain(chainA, receiptsA, ancientLimit); err != nil {
if n, err := chain.InsertReceiptChain(chainA, types.EncodeBlockReceiptLists(receiptsA), ancientLimit); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err)
}
// If the common ancestor is below the ancient limit, rewind the chain head.
@ -4218,7 +4218,7 @@ func testChainReorgSnapSync(t *testing.T, ancientLimit uint64) {
rawdb.WriteLastPivotNumber(db, ancestor)
chain.SetHead(ancestor)
}
if n, err := chain.InsertReceiptChain(chainB, receiptsB, ancientLimit); err != nil {
if n, err := chain.InsertReceiptChain(chainB, types.EncodeBlockReceiptLists(receiptsB), ancientLimit); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err)
}
head := chain.CurrentSnapBlock()
@ -4336,7 +4336,7 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64,
if n, err := chain.InsertHeadersBeforeCutoff(headersBefore); err != nil {
t.Fatalf("failed to insert headers before cutoff %d: %v", n, err)
}
if n, err := chain.InsertReceiptChain(blocksAfter, receiptsAfter, ancientLimit); err != nil {
if n, err := chain.InsertReceiptChain(blocksAfter, types.EncodeBlockReceiptLists(receiptsAfter), ancientLimit); err != nil {
t.Fatalf("failed to insert receipt %d: %v", n, err)
}
headSnap := chain.CurrentSnapBlock()

View file

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

View file

@ -227,7 +227,7 @@ type Config struct {
// NewFilterMaps creates a new FilterMaps and starts the indexer.
func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, finalBlock uint64, params Params, config Config) *FilterMaps {
rs, initialized, err := rawdb.ReadFilterMapsRange(db)
if err != nil || rs.Version != databaseVersion {
if err != nil || (initialized && rs.Version != databaseVersion) {
rs, initialized = rawdb.FilterMapsRange{}, false
log.Warn("Invalid log index database version; resetting log index")
}

View file

@ -515,7 +515,7 @@ func (tc *testChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
return tc.receipts[hash]
}
func (tc *testChain) GetRawReceiptsByHash(hash common.Hash) types.Receipts {
func (tc *testChain) GetRawReceipts(hash common.Hash, number uint64) types.Receipts {
tc.lock.RLock()
defer tc.lock.RUnlock()

View file

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

View file

@ -377,7 +377,11 @@ func TestBlockReceiptStorage(t *testing.T) {
t.Fatalf("receipts returned when body was deleted: %v", rs)
}
// Ensure that receipts without metadata can be returned without the block body too
if err := checkReceiptsRLP(ReadRawReceipts(db, hash, 0), receipts); err != nil {
raw := ReadRawReceipts(db, hash, 0)
for _, r := range raw {
r.Bloom = types.CreateBloom(r)
}
if err := checkReceiptsRLP(raw, receipts); err != nil {
t.Fatal(err)
}
// Sanity check that body alone without the receipt is a full purge
@ -439,7 +443,7 @@ func TestAncientStorage(t *testing.T) {
}
// Write and verify the header in the database
WriteAncientBlocks(db, []*types.Block{block}, []types.Receipts{nil})
WriteAncientBlocks(db, []*types.Block{block}, types.EncodeBlockReceiptLists([]types.Receipts{nil}))
if blob := ReadHeaderRLP(db, hash, number); len(blob) == 0 {
t.Fatalf("no header returned")
@ -609,7 +613,7 @@ func BenchmarkWriteAncientBlocks(b *testing.B) {
blocks := allBlocks[i : i+length]
receipts := batchReceipts[:length]
writeSize, err := WriteAncientBlocks(db, blocks, receipts)
writeSize, err := WriteAncientBlocks(db, blocks, types.EncodeBlockReceiptLists(receipts))
if err != nil {
b.Fatal(err)
}
@ -909,7 +913,7 @@ func TestHeadersRLPStorage(t *testing.T) {
}
receipts := make([]types.Receipts, 100)
// Write first half to ancients
WriteAncientBlocks(db, chain[:50], receipts[:50])
WriteAncientBlocks(db, chain[:50], types.EncodeBlockReceiptLists(receipts[:50]))
// Write second half to db
for i := 50; i < 100; i++ {
WriteCanonicalHash(db, chain[i].Hash(), chain[i].NumberU64())

View file

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

View file

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

View file

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

View file

@ -175,26 +175,27 @@ func NewDatabaseForTesting() *CachingDB {
func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
var readers []StateReader
// Set up the state snapshot reader if available. This feature
// is optional and may be partially useful if it's not fully
// generated.
if db.snap != nil {
// If standalone state snapshot is available (hash scheme),
// then construct the legacy snap reader.
// Configure the state reader using the standalone snapshot in hash mode.
// This reader offers improved performance but is optional and only
// partially useful if the snapshot is not fully generated.
if db.TrieDB().Scheme() == rawdb.HashScheme && db.snap != nil {
snap := db.snap.Snapshot(stateRoot)
if snap != nil {
readers = append(readers, newFlatReader(snap))
}
} else {
// If standalone state snapshot is not available, try to construct
// the state reader with database.
}
// Configure the state reader using the path database in path mode.
// This reader offers improved performance but is optional and only
// partially useful if the snapshot data in path database is not
// fully generated.
if db.TrieDB().Scheme() == rawdb.PathScheme {
reader, err := db.triedb.StateReader(stateRoot)
if err == nil {
readers = append(readers, newFlatReader(reader)) // state reader is optional
readers = append(readers, newFlatReader(reader))
}
}
// Set up the trie reader, which is expected to always be available
// as the gatekeeper unless the state is corrupted.
// Configure the trie reader, which is expected to be available as the
// gatekeeper unless the state is corrupted.
tr, err := newTrieReader(stateRoot, db.triedb, db.pointCache)
if err != nil {
return nil, err

View file

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

View file

@ -562,9 +562,8 @@ func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common
// updateStateObject writes the given object to the trie.
func (s *StateDB) updateStateObject(obj *stateObject) {
// Encode the account and update the account trie
addr := obj.Address()
if err := s.trie.UpdateAccount(addr, &obj.data, len(obj.code)); err != nil {
s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err))
if err := s.trie.UpdateAccount(obj.Address(), &obj.data, len(obj.code)); err != nil {
s.setError(fmt.Errorf("updateStateObject (%x) error: %v", obj.Address(), err))
}
if obj.dirtyCode {
s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code)

View file

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

View file

@ -457,7 +457,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
st.evm.AccessEvents.AddTxOrigin(msg.From)
if targetAddr := msg.To; targetAddr != nil {
st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0)
st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0, !st.state.Exist(*targetAddr))
}
}
@ -552,7 +552,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// add the coinbase to the witness iff the fee is greater than 0
if rules.IsEIP4762 && fee.Sign() != 0 {
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true)
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64)
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -206,8 +206,14 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) {
// add proof of absence to witness
wgas := evm.AccessEvents.AddAccount(addr, false)
// Add proof of absence to witness
// At this point, the read costs have already been charged, either because this
// is a direct tx call, in which case it's covered by the intrinsic gas, or because
// of a CALL instruction, in which case BASIC_DATA has been added to the access
// list in write mode. If there is enough gas paying for the addition of the code
// hash leaf to the access list, then account creation will proceed unimpaired.
// Thus, only pay for the creation of the code hash leaf here.
wgas := evm.AccessEvents.CodeHashGas(addr, true, gas, false)
if gas < wgas {
evm.StateDB.RevertToSnapshot(snapshot)
return nil, 0, ErrOutOfGas
@ -433,7 +439,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
// Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 {
statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address)
statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas)
if statelessGas > gas {
return nil, common.Address{}, 0, ErrOutOfGas
}
@ -481,14 +487,14 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
}
// Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 {
statelessGas := evm.AccessEvents.ContractCreateInitGas(address)
if statelessGas > gas {
consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas)
if consumed < wanted {
return nil, common.Address{}, 0, ErrOutOfGas
}
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, gas-statelessGas, tracing.GasChangeWitnessContractInit)
evm.Config.Tracer.OnGasChange(gas, gas-consumed, tracing.GasChangeWitnessContractInit)
}
gas = gas - statelessGas
gas = gas - consumed
}
evm.Context.Transfer(evm.StateDB, caller, address, value)
@ -535,7 +541,9 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
return ret, ErrCodeStoreOutOfGas
}
} else {
if len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) {
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas)
contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
if len(ret) > 0 && (consumed < wanted) {
return ret, ErrCodeStoreOutOfGas
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -38,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/params/forks"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
@ -196,11 +197,11 @@ func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI {
// and return its payloadID.
func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if payloadAttributes != nil {
if payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil {
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("withdrawals and beacon root not supported in V1"))
}
if api.eth.BlockChain().Config().IsShanghai(api.eth.BlockChain().Config().LondonBlock, payloadAttributes.Timestamp) {
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("forkChoiceUpdateV1 called post-shanghai"))
switch {
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1")
case !api.checkFork(payloadAttributes.Timestamp, forks.Paris, forks.Shanghai):
return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai")
}
}
return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, false)
@ -210,20 +211,15 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, pa
// attributes. It supports both PayloadAttributesV1 and PayloadAttributesV2.
func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil {
if params.BeaconRoot != nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("unexpected beacon root"))
}
switch api.eth.BlockChain().Config().LatestFork(params.Timestamp) {
case forks.Paris:
if params.Withdrawals != nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("withdrawals before shanghai"))
}
case forks.Shanghai:
if params.Withdrawals == nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals"))
}
default:
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV2 must only be called with paris and shanghai payloads"))
switch {
case params.BeaconRoot != nil:
return engine.STATUS_INVALID, attributesErr("unexpected beacon root")
case api.checkFork(params.Timestamp, forks.Paris) && params.Withdrawals != nil:
return engine.STATUS_INVALID, attributesErr("withdrawals before shanghai")
case api.checkFork(params.Timestamp, forks.Shanghai) && params.Withdrawals == nil:
return engine.STATUS_INVALID, attributesErr("missing withdrawals")
case !api.checkFork(params.Timestamp, forks.Paris, forks.Shanghai):
return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads")
}
}
return api.forkchoiceUpdated(update, params, engine.PayloadV2, false)
@ -233,14 +229,13 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, pa
// in the payload attributes. It supports only PayloadAttributesV3.
func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil {
if params.Withdrawals == nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals"))
}
if params.BeaconRoot == nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing beacon root"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun && api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV3 must only be called for cancun payloads"))
switch {
case params.Withdrawals == nil:
return engine.STATUS_INVALID, attributesErr("missing withdrawals")
case params.BeaconRoot == nil:
return engine.STATUS_INVALID, attributesErr("missing beacon root")
case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague):
return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun or prague payloads")
}
}
// TODO(matt): the spec requires that fcu is applied when called on a valid
@ -254,11 +249,11 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
// generates an execution witness too if block building was requested.
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if payloadAttributes != nil {
if payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil {
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("withdrawals and beacon root not supported in V1"))
}
if api.eth.BlockChain().Config().IsShanghai(api.eth.BlockChain().Config().LondonBlock, payloadAttributes.Timestamp) {
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("forkChoiceUpdateV1 called post-shanghai"))
switch {
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1")
case !api.checkFork(payloadAttributes.Timestamp, forks.Paris, forks.Shanghai):
return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai")
}
}
return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, true)
@ -268,20 +263,15 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.Forkchoice
// generates an execution witness too if block building was requested.
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil {
if params.BeaconRoot != nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("unexpected beacon root"))
}
switch api.eth.BlockChain().Config().LatestFork(params.Timestamp) {
case forks.Paris:
if params.Withdrawals != nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("withdrawals before shanghai"))
}
case forks.Shanghai:
if params.Withdrawals == nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals"))
}
default:
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV2 must only be called with paris and shanghai payloads"))
switch {
case params.BeaconRoot != nil:
return engine.STATUS_INVALID, attributesErr("unexpected beacon root")
case api.checkFork(params.Timestamp, forks.Paris) && params.Withdrawals != nil:
return engine.STATUS_INVALID, attributesErr("withdrawals before shanghai")
case api.checkFork(params.Timestamp, forks.Shanghai) && params.Withdrawals == nil:
return engine.STATUS_INVALID, attributesErr("missing withdrawals")
case !api.checkFork(params.Timestamp, forks.Paris, forks.Shanghai):
return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads")
}
}
return api.forkchoiceUpdated(update, params, engine.PayloadV2, true)
@ -291,14 +281,13 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.Forkchoice
// generates an execution witness too if block building was requested.
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil {
if params.Withdrawals == nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals"))
}
if params.BeaconRoot == nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing beacon root"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun && api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV3 must only be called for cancun payloads"))
switch {
case params.Withdrawals == nil:
return engine.STATUS_INVALID, attributesErr("missing withdrawals")
case params.BeaconRoot == nil:
return engine.STATUS_INVALID, attributesErr("missing beacon root")
case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague):
return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun or prague payloads")
}
}
// TODO(matt): the spec requires that fcu is applied when called on a valid
@ -468,7 +457,7 @@ func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config engine.Transit
api.lastTransitionUpdate = time.Now()
api.lastTransitionLock.Unlock()
ttd := api.eth.BlockChain().Config().TerminalTotalDifficulty
ttd := api.config().TerminalTotalDifficulty
if ttd == nil || ttd.Cmp(config.TerminalTotalDifficulty.ToInt()) != 0 {
log.Warn("Invalid TTD configured", "geth", ttd, "beacon", config.TerminalTotalDifficulty)
return nil, fmt.Errorf("invalid ttd: execution %v consensus %v", ttd, config.TerminalTotalDifficulty)
@ -550,86 +539,74 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo
return res, nil
}
// Helper for NewPayload* methods.
var invalidStatus = engine.PayloadStatusV1{Status: engine.INVALID}
// NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
if params.Withdrawals != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
return invalidStatus, paramsErr("withdrawals not supported in V1")
}
return api.newPayload(params, nil, nil, nil, false)
}
// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
if api.eth.BlockChain().Config().IsCancun(api.eth.BlockChain().Config().LondonBlock, params.Timestamp) {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use newPayloadV2 post-cancun"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai {
if params.Withdrawals == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
}
} else {
if params.Withdrawals != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai"))
}
}
if params.ExcessBlobGas != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil excessBlobGas pre-cancun"))
}
if params.BlobGasUsed != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil blobGasUsed pre-cancun"))
var (
cancun = api.config().IsCancun(api.config().LondonBlock, params.Timestamp)
shanghai = api.config().IsShanghai(api.config().LondonBlock, params.Timestamp)
)
switch {
case cancun:
return invalidStatus, paramsErr("can't use newPayloadV2 post-cancun")
case shanghai && params.Withdrawals == nil:
return invalidStatus, paramsErr("nil withdrawals post-shanghai")
case !shanghai && params.Withdrawals != nil:
return invalidStatus, paramsErr("non-nil withdrawals pre-shanghai")
case params.ExcessBlobGas != nil:
return invalidStatus, paramsErr("non-nil excessBlobGas pre-cancun")
case params.BlobGasUsed != nil:
return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun")
}
return api.newPayload(params, nil, nil, nil, false)
}
// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
if params.Withdrawals == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
}
if params.ExcessBlobGas == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
}
if params.BlobGasUsed == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun"))
}
if versionedHashes == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
}
if beaconRoot == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 must only be called for cancun payloads"))
switch {
case params.Withdrawals == nil:
return invalidStatus, paramsErr("nil withdrawals post-shanghai")
case params.ExcessBlobGas == nil:
return invalidStatus, paramsErr("nil excessBlobGas post-cancun")
case params.BlobGasUsed == nil:
return invalidStatus, paramsErr("nil blobGasUsed post-cancun")
case versionedHashes == nil:
return invalidStatus, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
return invalidStatus, paramsErr("nil beaconRoot post-cancun")
case !api.checkFork(params.Timestamp, forks.Cancun):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
}
return api.newPayload(params, versionedHashes, beaconRoot, nil, false)
}
// NewPayloadV4 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes) (engine.PayloadStatusV1, error) {
if params.Withdrawals == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
}
if params.ExcessBlobGas == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
}
if params.BlobGasUsed == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun"))
}
if versionedHashes == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
}
if beaconRoot == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
}
if executionRequests == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil executionRequests post-prague"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV4 must only be called for prague payloads"))
switch {
case params.Withdrawals == nil:
return invalidStatus, paramsErr("nil withdrawals post-shanghai")
case params.ExcessBlobGas == nil:
return invalidStatus, paramsErr("nil excessBlobGas post-cancun")
case params.BlobGasUsed == nil:
return invalidStatus, paramsErr("nil blobGasUsed post-cancun")
case versionedHashes == nil:
return invalidStatus, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
return invalidStatus, paramsErr("nil beaconRoot post-cancun")
case executionRequests == nil:
return invalidStatus, paramsErr("nil executionRequests post-prague")
case !api.checkFork(params.Timestamp, forks.Prague):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
}
requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil {
@ -650,23 +627,21 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV1(params engine.ExecutableData) (
// NewPayloadWithWitnessV2 is analogous to NewPayloadV2, only it also generates
// and returns a stateless witness after running the payload.
func (api *ConsensusAPI) NewPayloadWithWitnessV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
if api.eth.BlockChain().Config().IsCancun(api.eth.BlockChain().Config().LondonBlock, params.Timestamp) {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use newPayloadV2 post-cancun"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai {
if params.Withdrawals == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
}
} else {
if params.Withdrawals != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai"))
}
}
if params.ExcessBlobGas != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil excessBlobGas pre-cancun"))
}
if params.BlobGasUsed != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil blobGasUsed pre-cancun"))
var (
cancun = api.config().IsCancun(api.config().LondonBlock, params.Timestamp)
shanghai = api.config().IsShanghai(api.config().LondonBlock, params.Timestamp)
)
switch {
case cancun:
return invalidStatus, paramsErr("can't use newPayloadV2 post-cancun")
case shanghai && params.Withdrawals == nil:
return invalidStatus, paramsErr("nil withdrawals post-shanghai")
case !shanghai && params.Withdrawals != nil:
return invalidStatus, paramsErr("non-nil withdrawals pre-shanghai")
case params.ExcessBlobGas != nil:
return invalidStatus, paramsErr("non-nil excessBlobGas pre-cancun")
case params.BlobGasUsed != nil:
return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun")
}
return api.newPayload(params, nil, nil, nil, true)
}
@ -674,25 +649,19 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV2(params engine.ExecutableData) (
// NewPayloadWithWitnessV3 is analogous to NewPayloadV3, only it also generates
// and returns a stateless witness after running the payload.
func (api *ConsensusAPI) NewPayloadWithWitnessV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
if params.Withdrawals == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
}
if params.ExcessBlobGas == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
}
if params.BlobGasUsed == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun"))
}
if versionedHashes == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
}
if beaconRoot == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadWithWitnessV3 must only be called for cancun payloads"))
switch {
case params.Withdrawals == nil:
return invalidStatus, paramsErr("nil withdrawals post-shanghai")
case params.ExcessBlobGas == nil:
return invalidStatus, paramsErr("nil excessBlobGas post-cancun")
case params.BlobGasUsed == nil:
return invalidStatus, paramsErr("nil blobGasUsed post-cancun")
case versionedHashes == nil:
return invalidStatus, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
return invalidStatus, paramsErr("nil beaconRoot post-cancun")
case !api.checkFork(params.Timestamp, forks.Cancun):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
}
return api.newPayload(params, versionedHashes, beaconRoot, nil, true)
}
@ -700,28 +669,21 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV3(params engine.ExecutableData, v
// NewPayloadWithWitnessV4 is analogous to NewPayloadV4, only it also generates
// and returns a stateless witness after running the payload.
func (api *ConsensusAPI) NewPayloadWithWitnessV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes) (engine.PayloadStatusV1, error) {
if params.Withdrawals == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
}
if params.ExcessBlobGas == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
}
if params.BlobGasUsed == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun"))
}
if versionedHashes == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
}
if beaconRoot == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
}
if executionRequests == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil executionRequests post-prague"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadWithWitnessV4 must only be called for prague payloads"))
switch {
case params.Withdrawals == nil:
return invalidStatus, paramsErr("nil withdrawals post-shanghai")
case params.ExcessBlobGas == nil:
return invalidStatus, paramsErr("nil excessBlobGas post-cancun")
case params.BlobGasUsed == nil:
return invalidStatus, paramsErr("nil blobGasUsed post-cancun")
case versionedHashes == nil:
return invalidStatus, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
return invalidStatus, paramsErr("nil beaconRoot post-cancun")
case executionRequests == nil:
return invalidStatus, paramsErr("nil executionRequests post-prague")
case !api.checkFork(params.Timestamp, forks.Prague):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
}
requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil {
@ -742,23 +704,21 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV1(params engine.ExecutableData,
// ExecuteStatelessPayloadV2 is analogous to NewPayloadV2, only it operates in
// a stateless mode on top of a provided witness instead of the local database.
func (api *ConsensusAPI) ExecuteStatelessPayloadV2(params engine.ExecutableData, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) {
if api.eth.BlockChain().Config().IsCancun(api.eth.BlockChain().Config().LondonBlock, params.Timestamp) {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use newPayloadV2 post-cancun"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai {
if params.Withdrawals == nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
}
} else {
if params.Withdrawals != nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai"))
}
}
if params.ExcessBlobGas != nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil excessBlobGas pre-cancun"))
}
if params.BlobGasUsed != nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil blobGasUsed pre-cancun"))
var (
cancun = api.config().IsCancun(api.config().LondonBlock, params.Timestamp)
shanghai = api.config().IsShanghai(api.config().LondonBlock, params.Timestamp)
)
switch {
case cancun:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("can't use newPayloadV2 post-cancun")
case shanghai && params.Withdrawals == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil withdrawals post-shanghai")
case !shanghai && params.Withdrawals != nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("non-nil withdrawals pre-shanghai")
case params.ExcessBlobGas != nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("non-nil excessBlobGas pre-cancun")
case params.BlobGasUsed != nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("non-nil blobGasUsed pre-cancun")
}
return api.executeStatelessPayload(params, nil, nil, nil, opaqueWitness)
}
@ -766,25 +726,19 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV2(params engine.ExecutableData,
// ExecuteStatelessPayloadV3 is analogous to NewPayloadV3, only it operates in
// a stateless mode on top of a provided witness instead of the local database.
func (api *ConsensusAPI) ExecuteStatelessPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) {
if params.Withdrawals == nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
}
if params.ExcessBlobGas == nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
}
if params.BlobGasUsed == nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun"))
}
if versionedHashes == nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
}
if beaconRoot == nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("executeStatelessPayloadV3 must only be called for cancun payloads"))
switch {
case params.Withdrawals == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil withdrawals post-shanghai")
case params.ExcessBlobGas == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil excessBlobGas post-cancun")
case params.BlobGasUsed == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil blobGasUsed post-cancun")
case versionedHashes == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun")
case !api.checkFork(params.Timestamp, forks.Cancun):
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
}
return api.executeStatelessPayload(params, versionedHashes, beaconRoot, nil, opaqueWitness)
}
@ -792,30 +746,26 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV3(params engine.ExecutableData,
// ExecuteStatelessPayloadV4 is analogous to NewPayloadV4, only it operates in
// a stateless mode on top of a provided witness instead of the local database.
func (api *ConsensusAPI) ExecuteStatelessPayloadV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) {
if params.Withdrawals == nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
}
if params.ExcessBlobGas == nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
}
if params.BlobGasUsed == nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun"))
}
if versionedHashes == nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
}
if beaconRoot == nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
}
if executionRequests == nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil executionRequests post-prague"))
}
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("executeStatelessPayloadV4 must only be called for prague payloads"))
switch {
case params.Withdrawals == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil withdrawals post-shanghai")
case params.ExcessBlobGas == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil excessBlobGas post-cancun")
case params.BlobGasUsed == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil blobGasUsed post-cancun")
case versionedHashes == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun")
case executionRequests == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil executionRequests post-prague")
case !api.checkFork(params.Timestamp, forks.Prague):
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
}
requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
}
return api.executeStatelessPayload(params, versionedHashes, beaconRoot, requests, opaqueWitness)
}
@ -981,7 +931,7 @@ func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, v
api.lastNewPayloadLock.Unlock()
log.Trace("Executing block statelessly", "number", block.Number(), "hash", params.BlockHash)
stateRoot, receiptRoot, err := core.ExecuteStateless(api.eth.BlockChain().Config(), vm.Config{}, block, witness)
stateRoot, receiptRoot, err := core.ExecuteStateless(api.config(), vm.Config{}, block, witness)
if err != nil {
log.Warn("ExecuteStatelessPayload: execution failed", "err", err)
errorMsg := err.Error()
@ -1119,7 +1069,7 @@ func (api *ConsensusAPI) heartbeat() {
time.Sleep(beaconUpdateStartupTimeout)
// If the network is not yet merged/merging, don't bother continuing.
if api.eth.BlockChain().Config().TerminalTotalDifficulty == nil {
if api.config().TerminalTotalDifficulty == nil {
return
}
@ -1163,6 +1113,23 @@ func (api *ConsensusAPI) heartbeat() {
}
}
// config retrieves the chain's fork configuration.
func (api *ConsensusAPI) config() *params.ChainConfig {
return api.eth.BlockChain().Config()
}
// checkFork returns true if the latest fork at the given timestamp
// is one of the forks provided.
func (api *ConsensusAPI) checkFork(timestamp uint64, forks ...forks.Fork) bool {
latest := api.config().LatestFork(timestamp)
for _, fork := range forks {
if latest == fork {
return true
}
}
return false
}
// ExchangeCapabilities returns the current methods provided by this node.
func (api *ConsensusAPI) ExchangeCapabilities([]string) []string {
return caps
@ -1288,3 +1255,21 @@ func validateRequests(requests [][]byte) error {
}
return nil
}
// paramsErr is a helper function for creating an InvalidPayloadAttributes
// Engine API error.
func paramsErr(msg string) error {
return engine.InvalidParams.With(errors.New(msg))
}
// attributesErr is a helper function for creating an InvalidPayloadAttributes
// Engine API error.
func attributesErr(msg string) error {
return engine.InvalidPayloadAttributes.With(errors.New(msg))
}
// unsupportedForkErr is a helper function for creating an UnsupportedFork
// Engine API error.
func unsupportedForkErr(msg string) error {
return engine.UnsupportedFork.With(errors.New(msg))
}

View file

@ -36,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/triedb"
)
@ -202,7 +203,7 @@ type BlockChain interface {
// into the local chain. Blocks older than the specified `ancientLimit`
// are stored directly in the ancient store, while newer blocks are stored
// in the live key-value store.
InsertReceiptChain(types.Blocks, []types.Receipts, uint64) (int, error)
InsertReceiptChain(types.Blocks, []rlp.RawValue, uint64) (int, error)
// Snapshots returns the blockchain snapshot tree to paused it during sync.
Snapshots() *snapshot.Tree
@ -1034,7 +1035,7 @@ func (d *Downloader) commitSnapSyncData(results []*fetchResult, stateSync *state
"lastnumn", last.Number, "lasthash", last.Hash(),
)
blocks := make([]*types.Block, len(results))
receipts := make([]types.Receipts, len(results))
receipts := make([]rlp.RawValue, len(results))
for i, result := range results {
blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.body())
receipts[i] = result.Receipts
@ -1051,7 +1052,7 @@ func (d *Downloader) commitPivotBlock(result *fetchResult) error {
log.Debug("Committing snap sync pivot as new head", "number", block.Number(), "hash", block.Hash())
// Commit the pivot block as the new head, will require full sync from here on
if _, err := d.blockchain.InsertReceiptChain([]*types.Block{block}, []types.Receipts{result.Receipts}, d.ancientLimit); err != nil {
if _, err := d.blockchain.InsertReceiptChain([]*types.Block{block}, []rlp.RawValue{result.Receipts}, d.ancientLimit); err != nil {
return err
}
if err := d.blockchain.SnapSyncCommitHead(block.Hash()); err != nil {

View file

@ -255,23 +255,24 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
// peer in the download tester. The returned function can be used to retrieve
// batches of block receipts from the particularly requested peer.
func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) {
blobs := eth.ServiceGetReceiptsQuery(dlp.chain, hashes)
blobs := eth.ServiceGetReceiptsQuery68(dlp.chain, hashes)
receipts := make([][]*types.Receipt, len(blobs))
receipts := make([]types.Receipts, len(blobs))
for i, blob := range blobs {
rlp.DecodeBytes(blob, &receipts[i])
}
hasher := trie.NewStackTrie(nil)
hashes = make([]common.Hash, len(receipts))
for i, receipt := range receipts {
hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher)
hashes[i] = types.DeriveSha(receipt, hasher)
}
req := &eth.Request{
Peer: dlp.id,
}
resp := eth.ReceiptsRLPResponse(types.EncodeBlockReceiptLists(receipts))
res := &eth.Response{
Req: req,
Res: (*eth.ReceiptsResponse)(&receipts),
Res: &resp,
Meta: hashes,
Time: 1,
Done: make(chan error, 1), // Ignore the returned status

View file

@ -88,7 +88,7 @@ func (q *receiptQueue) request(peer *peerConnection, req *fetchRequest, resCh ch
// deliver is responsible for taking a generic response packet from the concurrent
// fetcher, unpacking the receipt data and delivering it to the downloader's queue.
func (q *receiptQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) {
receipts := *packet.Res.(*eth.ReceiptsResponse)
receipts := *packet.Res.(*eth.ReceiptsRLPResponse)
hashes := packet.Meta.([]common.Hash) // {receipt hashes}
accepted, err := q.queue.DeliverReceipts(peer.id, receipts, hashes)

View file

@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
)
const (
@ -69,7 +70,7 @@ type fetchResult struct {
Header *types.Header
Uncles []*types.Header
Transactions types.Transactions
Receipts types.Receipts
Receipts rlp.RawValue
Withdrawals types.Withdrawals
}
@ -318,9 +319,7 @@ func (q *queue) Results(block bool) []*fetchResult {
for _, uncle := range result.Uncles {
size += uncle.Size()
}
for _, receipt := range result.Receipts {
size += receipt.Size()
}
size += common.StorageSize(len(result.Receipts))
for _, tx := range result.Transactions {
size += common.StorageSize(tx.Size())
}
@ -631,7 +630,7 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH
// DeliverReceipts injects a receipt retrieval response into the results queue.
// The method returns the number of transaction receipts accepted from the delivery
// and also wakes any threads waiting for data delivery.
func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt, receiptListHashes []common.Hash) (int, error) {
func (q *queue) DeliverReceipts(id string, receiptList []rlp.RawValue, receiptListHashes []common.Hash) (int, error) {
q.lock.Lock()
defer q.lock.Unlock()

View file

@ -358,16 +358,16 @@ func XTestDelivery(t *testing.T) {
for {
f, _, _ := q.ReserveReceipts(peer, rand.Intn(50))
if f != nil {
var rcs [][]*types.Receipt
var rcs []types.Receipts
for _, hdr := range f.Headers {
rcs = append(rcs, world.getReceipts(hdr.Number.Uint64()))
}
hasher := trie.NewStackTrie(nil)
hashes := make([]common.Hash, len(rcs))
for i, receipt := range rcs {
hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher)
hashes[i] = types.DeriveSha(receipt, hasher)
}
_, err := q.DeliverReceipts(peer.id, rcs, hashes)
_, err := q.DeliverReceipts(peer.id, types.EncodeBlockReceiptLists(rcs), hashes)
if err != nil {
fmt.Printf("delivered %d receipts %v\n", len(rcs), err)
}

View file

@ -80,11 +80,8 @@ func (b *testBackend) GetReceiptsByHash(hash common.Hash) types.Receipts {
return r
}
func (b *testBackend) GetRawReceiptsByHash(hash common.Hash) types.Receipts {
if number := rawdb.ReadHeaderNumber(b.db, hash); number != nil {
return rawdb.ReadRawReceipts(b.db, hash, *number)
}
return nil
func (b *testBackend) GetRawReceipts(hash common.Hash, number uint64) types.Receipts {
return rawdb.ReadRawReceipts(b.db, hash, number)
}
func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) {

View file

@ -18,15 +18,17 @@ package eth
import (
"errors"
"maps"
"math"
"math/big"
"slices"
"sync"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/forkid"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
@ -48,6 +50,9 @@ const (
// The number is referenced from the size of tx pool.
txChanSize = 4096
// chainHeadChanSize is the size of channel listening to ChainHeadEvent.
chainHeadChanSize = 128
// txMaxBroadcastSize is the max size of a transaction that will be broadcasted.
// All transactions with a higher size will be announced and need to be fetched
// by the peer.
@ -103,9 +108,8 @@ type handlerConfig struct {
}
type handler struct {
nodeID enode.ID
networkID uint64
forkFilter forkid.Filter // Fork ID filter, constant across the lifetime of the node
nodeID enode.ID
networkID uint64
snapSync atomic.Bool // Flag whether snap sync is enabled (gets disabled if we already have blocks)
synced atomic.Bool // Flag whether we're considered synchronised (enables transaction processing)
@ -119,9 +123,10 @@ type handler struct {
txFetcher *fetcher.TxFetcher
peers *peerSet
eventMux *event.TypeMux
txsCh chan core.NewTxsEvent
txsSub event.Subscription
eventMux *event.TypeMux
txsCh chan core.NewTxsEvent
txsSub event.Subscription
blockRange *blockRangeState
requiredBlocks map[uint64]common.Hash
@ -143,7 +148,6 @@ func newHandler(config *handlerConfig) (*handler, error) {
h := &handler{
nodeID: config.NodeID,
networkID: config.Network,
forkFilter: forkid.NewFilter(config.Chain),
eventMux: config.EventMux,
database: config.Database,
txpool: config.TxPool,
@ -183,7 +187,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
}
}
// If snap sync is requested but snapshots are disabled, fail loudly
if h.snapSync.Load() && config.Chain.Snapshots() == nil {
if h.snapSync.Load() && (config.Chain.Snapshots() == nil && config.Chain.TrieDB().Scheme() == rawdb.HashScheme) {
return nil, errors.New("snap sync not supported with snapshots disabled")
}
// Construct the downloader (long sync)
@ -256,14 +260,7 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error {
}
// Execute the Ethereum handshake
var (
genesis = h.chain.Genesis()
head = h.chain.CurrentHeader()
hash = head.Hash()
number = head.Number.Uint64()
)
forkID := forkid.NewID(h.chain.Config(), genesis, number, head.Time)
if err := peer.Handshake(h.networkID, hash, genesis.Hash(), forkID, h.forkFilter); err != nil {
if err := peer.Handshake(h.networkID, h.chain, h.blockRange.currentRange()); err != nil {
peer.Log().Debug("Ethereum handshake failed", "err", err)
return err
}
@ -434,6 +431,11 @@ func (h *handler) Start(maxPeers int) {
h.txsSub = h.txpool.SubscribeTransactions(h.txsCh, false)
go h.txBroadcastLoop()
// broadcast block range
h.wg.Add(1)
h.blockRange = newBlockRangeState(h.chain, h.eventMux)
go h.blockRangeLoop(h.blockRange)
// start sync handlers
h.txFetcher.Start()
@ -444,6 +446,7 @@ func (h *handler) Start(maxPeers int) {
func (h *handler) Stop() {
h.txsSub.Unsubscribe() // quits txBroadcastLoop
h.blockRange.stop()
h.txFetcher.Stop()
h.downloader.Terminate()
@ -565,3 +568,129 @@ func (h *handler) enableSyncedFeatures() {
h.snapSync.Store(false)
}
}
// blockRangeState holds the state of the block range update broadcasting mechanism.
type blockRangeState struct {
prev eth.BlockRangeUpdatePacket
next atomic.Pointer[eth.BlockRangeUpdatePacket]
headCh chan core.ChainHeadEvent
headSub event.Subscription
syncSub *event.TypeMuxSubscription
}
func newBlockRangeState(chain *core.BlockChain, typeMux *event.TypeMux) *blockRangeState {
headCh := make(chan core.ChainHeadEvent, chainHeadChanSize)
headSub := chain.SubscribeChainHeadEvent(headCh)
syncSub := typeMux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
st := &blockRangeState{
headCh: headCh,
headSub: headSub,
syncSub: syncSub,
}
st.update(chain, chain.CurrentBlock())
st.prev = *st.next.Load()
return st
}
// blockRangeBroadcastLoop announces changes in locally-available block range to peers.
// The range to announce is the range that is available in the store, so it's not just
// about imported blocks.
func (h *handler) blockRangeLoop(st *blockRangeState) {
defer h.wg.Done()
for {
select {
case ev := <-st.syncSub.Chan():
if ev == nil {
continue
}
if _, ok := ev.Data.(downloader.StartEvent); ok && h.snapSync.Load() {
h.blockRangeWhileSnapSyncing(st)
}
case <-st.headCh:
st.update(h.chain, h.chain.CurrentBlock())
if st.shouldSend() {
h.broadcastBlockRange(st)
}
case <-st.headSub.Err():
return
}
}
}
// blockRangeWhileSnapSyncing announces block range updates during snap sync.
// Here we poll the CurrentSnapBlock on a timer and announce updates to it.
func (h *handler) blockRangeWhileSnapSyncing(st *blockRangeState) {
tick := time.NewTicker(1 * time.Minute)
defer tick.Stop()
for {
select {
case <-tick.C:
st.update(h.chain, h.chain.CurrentSnapBlock())
if st.shouldSend() {
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:
return
}
// ignore head updates, but exit when the subscription ends
case <-st.headCh:
case <-st.headSub.Err():
return
}
}
}
// broadcastBlockRange sends a range update when one is due.
func (h *handler) broadcastBlockRange(state *blockRangeState) {
h.peers.lock.Lock()
peerlist := slices.Collect(maps.Values(h.peers.peers))
h.peers.lock.Unlock()
if len(peerlist) == 0 {
return
}
msg := state.currentRange()
log.Debug("Sending BlockRangeUpdate", "peers", len(peerlist), "earliest", msg.EarliestBlock, "latest", msg.LatestBlock)
for _, p := range peerlist {
p.SendBlockRangeUpdate(msg)
}
state.prev = *state.next.Load()
}
// update assigns the values of the next block range update from the chain.
func (st *blockRangeState) update(chain *core.BlockChain, latest *types.Header) {
earliest, _ := chain.HistoryPruningCutoff()
st.next.Store(&eth.BlockRangeUpdatePacket{
EarliestBlock: min(latest.Number.Uint64(), earliest),
LatestBlock: latest.Number.Uint64(),
LatestBlockHash: latest.Hash(),
})
}
// shouldSend decides whether it is time to send a block range update. We don't want to
// send these updates constantly, so they will usually only be sent every 32 blocks.
// However, there is a special case: if the range would move back, i.e. due to SetHead, we
// want to send it immediately.
func (st *blockRangeState) shouldSend() bool {
next := st.next.Load()
return next.LatestBlock < st.prev.LatestBlock ||
next.LatestBlock-st.prev.LatestBlock >= 32
}
func (st *blockRangeState) stop() {
st.syncSub.Unsubscribe()
st.headSub.Unsubscribe()
}
// currentRange returns the current block range.
// This is safe to call from any goroutine.
func (st *blockRangeState) currentRange() eth.BlockRangeUpdatePacket {
return *st.next.Load()
}

View file

@ -25,7 +25,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/forkid"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
@ -257,11 +256,7 @@ func testRecvTransactions(t *testing.T, protocol uint) {
return eth.Handle((*ethHandler)(handler.handler), peer)
})
// Run the handshake locally to avoid spinning up a source handler
var (
genesis = handler.chain.Genesis()
head = handler.chain.CurrentBlock()
)
if err := src.Handshake(1, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil {
if err := src.Handshake(1, handler.chain, eth.BlockRangeUpdatePacket{}); err != nil {
t.Fatalf("failed to run protocol handshake")
}
// Send the transaction to the sink and verify that it's added to the tx pool
@ -316,11 +311,7 @@ func testSendTransactions(t *testing.T, protocol uint) {
return eth.Handle((*ethHandler)(handler.handler), peer)
})
// Run the handshake locally to avoid spinning up a source handler
var (
genesis = handler.chain.Genesis()
head = handler.chain.CurrentBlock()
)
if err := sink.Handshake(1, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil {
if err := sink.Handshake(1, handler.chain, eth.BlockRangeUpdatePacket{}); err != nil {
t.Fatalf("failed to run protocol handshake")
}
// After the handshake completes, the source handler should stream the sink

View file

@ -17,6 +17,7 @@
package eth
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/eth/protocols/snap"
)
@ -25,6 +26,13 @@ import (
// about a connected peer.
type ethPeerInfo struct {
Version uint `json:"version"` // Ethereum protocol version negotiated
*peerBlockRange
}
type peerBlockRange struct {
Earliest uint64 `json:"earliestBlock"`
Latest uint64 `json:"latestBlock"`
LatestHash common.Hash `json:"latestBlockHash"`
}
// ethPeer is a wrapper around eth.Peer to maintain a few extra metadata.
@ -35,9 +43,15 @@ type ethPeer struct {
// info gathers and returns some `eth` protocol metadata known about a peer.
func (p *ethPeer) info() *ethPeerInfo {
return &ethPeerInfo{
Version: p.Version(),
info := &ethPeerInfo{Version: p.Version()}
if br := p.BlockRange(); br != nil {
info.peerBlockRange = &peerBlockRange{
Earliest: br.EarliestBlock,
Latest: br.LatestBlock,
LatestHash: br.LatestBlockHash,
}
}
return info
}
// snapPeerInfo represents a short summary of the `snap` sub-protocol metadata known

View file

@ -175,12 +175,26 @@ var eth68 = map[uint64]msgHandler{
BlockHeadersMsg: handleBlockHeaders,
GetBlockBodiesMsg: handleGetBlockBodies,
BlockBodiesMsg: handleBlockBodies,
GetReceiptsMsg: handleGetReceipts,
ReceiptsMsg: handleReceipts,
GetReceiptsMsg: handleGetReceipts68,
ReceiptsMsg: handleReceipts[*ReceiptList68],
GetPooledTransactionsMsg: handleGetPooledTransactions,
PooledTransactionsMsg: handlePooledTransactions,
}
var eth69 = map[uint64]msgHandler{
TransactionsMsg: handleTransactions,
NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes,
GetBlockHeadersMsg: handleGetBlockHeaders,
BlockHeadersMsg: handleBlockHeaders,
GetBlockBodiesMsg: handleGetBlockBodies,
BlockBodiesMsg: handleBlockBodies,
GetReceiptsMsg: handleGetReceipts69,
ReceiptsMsg: handleReceipts[*ReceiptList69],
GetPooledTransactionsMsg: handleGetPooledTransactions,
PooledTransactionsMsg: handlePooledTransactions,
BlockRangeUpdateMsg: handleBlockRangeUpdate,
}
// handleMessage is invoked whenever an inbound message is received from a remote
// peer. The remote connection is torn down upon returning any error.
func handleMessage(backend Backend, peer *Peer) error {
@ -194,7 +208,14 @@ func handleMessage(backend Backend, peer *Peer) error {
}
defer msg.Discard()
var handlers = eth68
var handlers map[uint64]msgHandler
if peer.version == ETH68 {
handlers = eth68
} else if peer.version == ETH69 {
handlers = eth69
} else {
return fmt.Errorf("unknown eth protocol version: %v", peer.version)
}
// Track the amount of time it takes to serve the request and run the handler
if metrics.Enabled() {

View file

@ -529,22 +529,23 @@ func testGetBlockReceipts(t *testing.T, protocol uint) {
// Collect the hashes to request, and the response to expect
var (
hashes []common.Hash
receipts [][]*types.Receipt
receipts []*ReceiptList68
)
for i := uint64(0); i <= backend.chain.CurrentBlock().Number.Uint64(); i++ {
block := backend.chain.GetBlockByNumber(i)
hashes = append(hashes, block.Hash())
receipts = append(receipts, backend.chain.GetReceiptsByHash(block.Hash()))
trs := backend.chain.GetReceiptsByHash(block.Hash())
receipts = append(receipts, NewReceiptList68(trs))
}
// Send the hash request and verify the response
p2p.Send(peer.app, GetReceiptsMsg, &GetReceiptsPacket{
RequestId: 123,
GetReceiptsRequest: hashes,
})
if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, &ReceiptsPacket{
RequestId: 123,
ReceiptsResponse: receipts,
if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, &ReceiptsPacket[*ReceiptList68]{
RequestId: 123,
List: receipts,
}); err != nil {
t.Errorf("receipts mismatch: %v", err)
}
@ -612,10 +613,10 @@ func setup() (*testBackend, *testPeer) {
}
func FuzzEthProtocolHandlers(f *testing.F) {
handlers := eth68
handlers := eth69
backend, peer := setup()
f.Fuzz(func(t *testing.T, code byte, msg []byte) {
handler := handlers[uint64(code)%protocolLengths[ETH68]]
handler := handlers[uint64(code)%protocolLengths[ETH69]]
if handler == nil {
return
}

View file

@ -20,20 +20,25 @@ import (
"encoding/json"
"errors"
"fmt"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/tracker"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
// requestTracker is a singleton tracker for eth/66 and newer request times.
var requestTracker = tracker.New(ProtocolName, 5*time.Minute)
func handleGetBlockHeaders(backend Backend, msg Decoder, peer *Peer) error {
// Decode the complex header query
var query GetBlockHeadersPacket
if err := msg.Decode(&query); err != nil {
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
return err
}
response := ServiceGetBlockHeadersQuery(backend.Chain(), query.GetBlockHeadersRequest, peer)
return peer.ReplyBlockHeadersRLP(query.RequestId, response)
@ -216,7 +221,7 @@ func handleGetBlockBodies(backend Backend, msg Decoder, peer *Peer) error {
// Decode the block body retrieval message
var query GetBlockBodiesPacket
if err := msg.Decode(&query); err != nil {
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
return err
}
response := ServiceGetBlockBodiesQuery(backend.Chain(), query.GetBlockBodiesRequest)
return peer.ReplyBlockBodiesRLP(query.RequestId, response)
@ -243,19 +248,29 @@ func ServiceGetBlockBodiesQuery(chain *core.BlockChain, query GetBlockBodiesRequ
return bodies
}
func handleGetReceipts(backend Backend, msg Decoder, peer *Peer) error {
func handleGetReceipts68(backend Backend, msg Decoder, peer *Peer) error {
// Decode the block receipts retrieval message
var query GetReceiptsPacket
if err := msg.Decode(&query); err != nil {
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
return err
}
response := ServiceGetReceiptsQuery(backend.Chain(), query.GetReceiptsRequest)
response := ServiceGetReceiptsQuery68(backend.Chain(), query.GetReceiptsRequest)
return peer.ReplyReceiptsRLP(query.RequestId, response)
}
// ServiceGetReceiptsQuery assembles the response to a receipt query. It is
func handleGetReceipts69(backend Backend, msg Decoder, peer *Peer) error {
// Decode the block receipts retrieval message
var query GetReceiptsPacket
if err := msg.Decode(&query); err != nil {
return err
}
response := serviceGetReceiptsQuery69(backend.Chain(), query.GetReceiptsRequest)
return peer.ReplyReceiptsRLP(query.RequestId, response)
}
// ServiceGetReceiptsQuery68 assembles the response to a receipt query. It is
// exposed to allow external packages to test protocol behavior.
func ServiceGetReceiptsQuery(chain *core.BlockChain, query GetReceiptsRequest) []rlp.RawValue {
func ServiceGetReceiptsQuery68(chain *core.BlockChain, query GetReceiptsRequest) []rlp.RawValue {
// Gather state data until the fetch or network limits is reached
var (
bytes int
@ -267,19 +282,62 @@ func ServiceGetReceiptsQuery(chain *core.BlockChain, query GetReceiptsRequest) [
break
}
// Retrieve the requested block's receipts
results := chain.GetReceiptsByHash(hash)
results := chain.GetReceiptsRLP(hash)
if results == nil {
if header := chain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
continue
}
}
// If known, encode and queue for response packet
if encoded, err := rlp.EncodeToBytes(results); err != nil {
log.Error("Failed to encode receipt", "err", err)
} else {
receipts = append(receipts, encoded)
bytes += len(encoded)
body := chain.GetBodyRLP(hash)
if body == nil {
continue
}
var err error
results, err = blockReceiptsToNetwork68(results, body)
if err != nil {
log.Error("Error in block receipts conversion", "hash", hash, "err", err)
continue
}
}
receipts = append(receipts, results)
bytes += len(results)
}
return receipts
}
// serviceGetReceiptsQuery69 assembles the response to a receipt query.
// It does not send the bloom filters for the receipts
func serviceGetReceiptsQuery69(chain *core.BlockChain, query GetReceiptsRequest) []rlp.RawValue {
// Gather state data until the fetch or network limits is reached
var (
bytes int
receipts []rlp.RawValue
)
for lookups, hash := range query {
if bytes >= softResponseLimit || len(receipts) >= maxReceiptsServe ||
lookups >= 2*maxReceiptsServe {
break
}
// Retrieve the requested block's receipts
results := chain.GetReceiptsRLP(hash)
if results == nil {
if header := chain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
continue
}
} else {
body := chain.GetBodyRLP(hash)
if body == nil {
continue
}
var err error
results, err = blockReceiptsToNetwork69(results, body)
if err != nil {
log.Error("Error in block receipts conversion", "hash", hash, "err", err)
continue
}
}
receipts = append(receipts, results)
bytes += len(results)
}
return receipts
}
@ -296,7 +354,7 @@ func handleBlockHeaders(backend Backend, msg Decoder, peer *Peer) error {
// A batch of headers arrived to one of our previous requests
res := new(BlockHeadersPacket)
if err := msg.Decode(res); err != nil {
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
return err
}
metadata := func() interface{} {
hashes := make([]common.Hash, len(res.BlockHeadersRequest))
@ -316,7 +374,7 @@ func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error {
// A batch of block bodies arrived to one of our previous requests
res := new(BlockBodiesPacket)
if err := msg.Decode(res); err != nil {
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
return err
}
metadata := func() interface{} {
var (
@ -341,24 +399,35 @@ func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error {
}, metadata)
}
func handleReceipts(backend Backend, msg Decoder, peer *Peer) error {
func handleReceipts[L ReceiptsList](backend Backend, msg Decoder, peer *Peer) error {
// A batch of receipts arrived to one of our previous requests
res := new(ReceiptsPacket)
res := new(ReceiptsPacket[L])
if err := msg.Decode(res); err != nil {
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
return err
}
// Assign temporary hashing buffer to each list item, the same buffer is shared
// between all receipt list instances.
buffers := new(receiptListBuffers)
for i := range res.List {
res.List[i].setBuffers(buffers)
}
metadata := func() interface{} {
hasher := trie.NewStackTrie(nil)
hashes := make([]common.Hash, len(res.ReceiptsResponse))
for i, receipt := range res.ReceiptsResponse {
hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher)
hashes := make([]common.Hash, len(res.List))
for i := range res.List {
hashes[i] = types.DeriveSha(res.List[i], hasher)
}
return hashes
}
var enc ReceiptsRLPResponse
for i := range res.List {
enc = append(enc, res.List[i].EncodeForStorage())
}
return peer.dispatchResponse(&Response{
id: res.RequestId,
code: ReceiptsMsg,
Res: &res.ReceiptsResponse,
Res: &enc,
}, metadata)
}
@ -370,10 +439,10 @@ func handleNewPooledTransactionHashes(backend Backend, msg Decoder, peer *Peer)
}
ann := new(NewPooledTransactionHashesPacket)
if err := msg.Decode(ann); err != nil {
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
return err
}
if len(ann.Hashes) != len(ann.Types) || len(ann.Hashes) != len(ann.Sizes) {
return fmt.Errorf("%w: message %v: invalid len of fields: %v %v %v", errDecode, msg, len(ann.Hashes), len(ann.Types), len(ann.Sizes))
return fmt.Errorf("NewPooledTransactionHashes: invalid len of fields in %v %v %v", len(ann.Hashes), len(ann.Types), len(ann.Sizes))
}
// Schedule all the unknown hashes for retrieval
for _, hash := range ann.Hashes {
@ -386,7 +455,7 @@ func handleGetPooledTransactions(backend Backend, msg Decoder, peer *Peer) error
// Decode the pooled transactions retrieval message
var query GetPooledTransactionsPacket
if err := msg.Decode(&query); err != nil {
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
return err
}
hashes, txs := answerGetPooledTransactions(backend, query.GetPooledTransactionsRequest)
return peer.ReplyPooledTransactionsRLP(query.RequestId, hashes, txs)
@ -423,12 +492,12 @@ func handleTransactions(backend Backend, msg Decoder, peer *Peer) error {
// Transactions can be processed, parse all of them and deliver to the pool
var txs TransactionsPacket
if err := msg.Decode(&txs); err != nil {
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
return err
}
for i, tx := range txs {
// Validate and mark the remote transaction
if tx == nil {
return fmt.Errorf("%w: transaction %d is nil", errDecode, i)
return fmt.Errorf("Transactions: transaction %d is nil", i)
}
peer.markTransaction(tx.Hash())
}
@ -443,12 +512,12 @@ func handlePooledTransactions(backend Backend, msg Decoder, peer *Peer) error {
// Transactions can be processed, parse all of them and deliver to the pool
var txs PooledTransactionsPacket
if err := msg.Decode(&txs); err != nil {
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
return err
}
for i, tx := range txs.PooledTransactionsResponse {
// Validate and mark the remote transaction
if tx == nil {
return fmt.Errorf("%w: transaction %d is nil", errDecode, i)
return fmt.Errorf("PooledTransactions: transaction %d is nil", i)
}
peer.markTransaction(tx.Hash())
}
@ -456,3 +525,16 @@ func handlePooledTransactions(backend Backend, msg Decoder, peer *Peer) error {
return backend.Handle(peer, &txs.PooledTransactionsResponse)
}
func handleBlockRangeUpdate(backend Backend, msg Decoder, peer *Peer) error {
var update BlockRangeUpdatePacket
if err := msg.Decode(&update); err != nil {
return err
}
if err := update.Validate(); err != nil {
return err
}
// We don't do anything with these messages for now, just store them on the peer.
peer.lastRange.Store(&update)
return nil
}

View file

@ -19,10 +19,10 @@ package eth
import (
"errors"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/forkid"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p"
@ -36,44 +36,122 @@ const (
// Handshake executes the eth protocol handshake, negotiating version number,
// network IDs, difficulties, head and genesis blocks.
func (p *Peer) Handshake(network uint64, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error {
// Send out own handshake in a new thread
func (p *Peer) Handshake(networkID uint64, chain *core.BlockChain, rangeMsg BlockRangeUpdatePacket) error {
switch p.version {
case ETH69:
return p.handshake69(networkID, chain, rangeMsg)
case ETH68:
return p.handshake68(networkID, chain)
default:
return errors.New("unsupported protocol version")
}
}
func (p *Peer) handshake68(networkID uint64, chain *core.BlockChain) error {
var (
genesis = chain.Genesis()
latest = chain.CurrentBlock()
forkID = forkid.NewID(chain.Config(), genesis, latest.Number.Uint64(), latest.Time)
forkFilter = forkid.NewFilter(chain)
)
errc := make(chan error, 2)
var status StatusPacket // safe to read after two values have been received from errc
go func() {
errc <- p2p.Send(p.rw, StatusMsg, &StatusPacket{
pkt := &StatusPacket68{
ProtocolVersion: uint32(p.version),
NetworkID: network,
TD: new(big.Int), // unknown for post-merge tail=pruned networks
Head: head,
Genesis: genesis,
NetworkID: networkID,
Head: latest.Hash(),
Genesis: genesis.Hash(),
ForkID: forkID,
})
}()
go func() {
errc <- p.readStatus(network, &status, genesis, forkFilter)
}()
timeout := time.NewTimer(handshakeTimeout)
defer timeout.Stop()
for i := 0; i < 2; i++ {
select {
case err := <-errc:
if err != nil {
markError(p, err)
return err
}
case <-timeout.C:
markError(p, p2p.DiscReadTimeout)
return p2p.DiscReadTimeout
}
errc <- p2p.Send(p.rw, StatusMsg, pkt)
}()
var status StatusPacket68 // safe to read after two values have been received from errc
go func() {
errc <- p.readStatus68(networkID, &status, genesis.Hash(), forkFilter)
}()
return waitForHandshake(errc, p)
}
func (p *Peer) readStatus68(networkID uint64, status *StatusPacket68, genesis common.Hash, forkFilter forkid.Filter) error {
if err := p.readStatusMsg(status); err != nil {
return err
}
if status.NetworkID != networkID {
return fmt.Errorf("%w: %d (!= %d)", errNetworkIDMismatch, status.NetworkID, networkID)
}
if uint(status.ProtocolVersion) != p.version {
return fmt.Errorf("%w: %d (!= %d)", errProtocolVersionMismatch, status.ProtocolVersion, p.version)
}
if status.Genesis != genesis {
return fmt.Errorf("%w: %x (!= %x)", errGenesisMismatch, status.Genesis, genesis)
}
if err := forkFilter(status.ForkID); err != nil {
return fmt.Errorf("%w: %v", errForkIDRejected, err)
}
return nil
}
// readStatus reads the remote handshake message.
func (p *Peer) readStatus(network uint64, status *StatusPacket, genesis common.Hash, forkFilter forkid.Filter) error {
func (p *Peer) handshake69(networkID uint64, chain *core.BlockChain, rangeMsg BlockRangeUpdatePacket) error {
var (
genesis = chain.Genesis()
latest = chain.CurrentBlock()
forkID = forkid.NewID(chain.Config(), genesis, latest.Number.Uint64(), latest.Time)
forkFilter = forkid.NewFilter(chain)
)
errc := make(chan error, 2)
go func() {
pkt := &StatusPacket69{
ProtocolVersion: uint32(p.version),
NetworkID: networkID,
Genesis: genesis.Hash(),
ForkID: forkID,
EarliestBlock: rangeMsg.EarliestBlock,
LatestBlock: rangeMsg.LatestBlock,
LatestBlockHash: rangeMsg.LatestBlockHash,
}
errc <- p2p.Send(p.rw, StatusMsg, pkt)
}()
var status StatusPacket69 // safe to read after two values have been received from errc
go func() {
errc <- p.readStatus69(networkID, &status, genesis.Hash(), forkFilter)
}()
return waitForHandshake(errc, p)
}
func (p *Peer) readStatus69(networkID uint64, status *StatusPacket69, genesis common.Hash, forkFilter forkid.Filter) error {
if err := p.readStatusMsg(status); err != nil {
return err
}
if status.NetworkID != networkID {
return fmt.Errorf("%w: %d (!= %d)", errNetworkIDMismatch, status.NetworkID, networkID)
}
if uint(status.ProtocolVersion) != p.version {
return fmt.Errorf("%w: %d (!= %d)", errProtocolVersionMismatch, status.ProtocolVersion, p.version)
}
if status.Genesis != genesis {
return fmt.Errorf("%w: %x (!= %x)", errGenesisMismatch, status.Genesis, genesis)
}
if err := forkFilter(status.ForkID); err != nil {
return fmt.Errorf("%w: %v", errForkIDRejected, err)
}
// Handle initial block range.
initRange := &BlockRangeUpdatePacket{
EarliestBlock: status.EarliestBlock,
LatestBlock: status.LatestBlock,
LatestBlockHash: status.LatestBlockHash,
}
if err := initRange.Validate(); err != nil {
return fmt.Errorf("%w: %v", errInvalidBlockRange, err)
}
p.lastRange.Store(initRange)
return nil
}
// readStatusMsg reads the first message on the connection.
func (p *Peer) readStatusMsg(dst any) error {
msg, err := p.rw.ReadMsg()
if err != nil {
return err
@ -84,21 +162,26 @@ func (p *Peer) readStatus(network uint64, status *StatusPacket, genesis common.H
if msg.Size > maxMessageSize {
return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize)
}
// Decode the handshake and make sure everything matches
if err := msg.Decode(&status); err != nil {
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
if err := msg.Decode(dst); err != nil {
return err
}
if status.NetworkID != network {
return fmt.Errorf("%w: %d (!= %d)", errNetworkIDMismatch, status.NetworkID, network)
}
if uint(status.ProtocolVersion) != p.version {
return fmt.Errorf("%w: %d (!= %d)", errProtocolVersionMismatch, status.ProtocolVersion, p.version)
}
if status.Genesis != genesis {
return fmt.Errorf("%w: %x (!= %x)", errGenesisMismatch, status.Genesis, genesis)
}
if err := forkFilter(status.ForkID); err != nil {
return fmt.Errorf("%w: %v", errForkIDRejected, err)
return nil
}
func waitForHandshake(errc <-chan error, p *Peer) error {
timeout := time.NewTimer(handshakeTimeout)
defer timeout.Stop()
for range 2 {
select {
case err := <-errc:
if err != nil {
markError(p, err)
return err
}
case <-timeout.C:
markError(p, p2p.DiscReadTimeout)
return p2p.DiscReadTimeout
}
}
return nil
}
@ -124,3 +207,14 @@ func markError(p *Peer, err error) {
m.peerError.Mark(1)
}
}
// Validate checks basic validity of a block range announcement.
func (p *BlockRangeUpdatePacket) Validate() error {
if p.EarliestBlock > p.LatestBlock {
return errors.New("earliest > latest")
}
if p.LatestBlockHash == (common.Hash{}) {
return errors.New("zero latest hash")
}
return nil
}

View file

@ -52,19 +52,19 @@ func testHandshake(t *testing.T, protocol uint) {
want: errNoStatusMsg,
},
{
code: StatusMsg, data: StatusPacket{10, 1, new(big.Int), head.Hash(), genesis.Hash(), forkID},
code: StatusMsg, data: StatusPacket68{10, 1, new(big.Int), head.Hash(), genesis.Hash(), forkID},
want: errProtocolVersionMismatch,
},
{
code: StatusMsg, data: StatusPacket{uint32(protocol), 999, new(big.Int), head.Hash(), genesis.Hash(), forkID},
code: StatusMsg, data: StatusPacket68{uint32(protocol), 999, new(big.Int), head.Hash(), genesis.Hash(), forkID},
want: errNetworkIDMismatch,
},
{
code: StatusMsg, data: StatusPacket{uint32(protocol), 1, new(big.Int), head.Hash(), common.Hash{3}, forkID},
code: StatusMsg, data: StatusPacket68{uint32(protocol), 1, new(big.Int), head.Hash(), common.Hash{3}, forkID},
want: errGenesisMismatch,
},
{
code: StatusMsg, data: StatusPacket{uint32(protocol), 1, new(big.Int), head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}},
code: StatusMsg, data: StatusPacket68{uint32(protocol), 1, new(big.Int), head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}},
want: errForkIDRejected,
},
}
@ -80,7 +80,7 @@ func testHandshake(t *testing.T, protocol uint) {
// Send the junk test with one peer, check the handshake failure
go p2p.Send(app, test.code, test.data)
err := peer.Handshake(1, head.Hash(), genesis.Hash(), forkID, forkid.NewFilter(backend.chain))
err := peer.Handshake(1, backend.chain, BlockRangeUpdatePacket{})
if err == nil {
t.Errorf("test %d: protocol returned nil error, want %q", i, test.want)
} else if !errors.Is(err, test.want) {

View file

@ -18,6 +18,7 @@ package eth
import (
"math/rand"
"sync/atomic"
mapset "github.com/deckarep/golang-set/v2"
"github.com/ethereum/go-ethereum/common"
@ -47,6 +48,7 @@ type Peer struct {
*p2p.Peer // The embedded P2P package peer
rw p2p.MsgReadWriter // Input/output streams for snap
version uint // Protocol version negotiated
lastRange atomic.Pointer[BlockRangeUpdatePacket]
txpool TxPool // Transaction pool used by the broadcasters for liveness checks
knownTxs *knownCache // Set of transaction hashes known to be known by this peer
@ -102,6 +104,12 @@ func (p *Peer) Version() uint {
return p.version
}
// BlockRange returns the latest announced block range.
// This will be nil for peers below protocol version eth/69.
func (p *Peer) BlockRange() *BlockRangeUpdatePacket {
return p.lastRange.Load()
}
// KnownTransaction returns whether peer is known to already have a transaction.
func (p *Peer) KnownTransaction(hash common.Hash) bool {
return p.knownTxs.Contains(hash)
@ -343,6 +351,14 @@ func (p *Peer) RequestTxs(hashes []common.Hash) error {
})
}
// SendBlockRangeUpdate sends a notification about our available block range to the peer.
func (p *Peer) SendBlockRangeUpdate(msg BlockRangeUpdatePacket) error {
if p.version < ETH69 {
return nil
}
return p2p.Send(p.rw, BlockRangeUpdateMsg, &msg)
}
// knownCache is a cache for known hashes.
type knownCache struct {
hashes mapset.Set[common.Hash]

View file

@ -31,6 +31,7 @@ import (
// Constants to match up protocol versions and messages
const (
ETH68 = 68
ETH69 = 69
)
// ProtocolName is the official short name of the `eth` protocol used during
@ -39,11 +40,11 @@ const ProtocolName = "eth"
// ProtocolVersions are the supported versions of the `eth` protocol (first
// is primary).
var ProtocolVersions = []uint{ETH68}
var ProtocolVersions = []uint{ETH69, ETH68}
// protocolLengths are the number of implemented message corresponding to
// different protocol versions.
var protocolLengths = map[uint]uint64{ETH68: 17}
var protocolLengths = map[uint]uint64{ETH68: 17, ETH69: 18}
// maxMessageSize is the maximum cap on the size of a protocol message.
const maxMessageSize = 10 * 1024 * 1024
@ -62,17 +63,19 @@ const (
PooledTransactionsMsg = 0x0a
GetReceiptsMsg = 0x0f
ReceiptsMsg = 0x10
BlockRangeUpdateMsg = 0x11
)
var (
errNoStatusMsg = errors.New("no status message")
errMsgTooLarge = errors.New("message too long")
errDecode = errors.New("invalid message")
errInvalidMsgCode = errors.New("invalid message code")
errProtocolVersionMismatch = errors.New("protocol version mismatch")
errNetworkIDMismatch = errors.New("network ID mismatch")
errGenesisMismatch = errors.New("genesis mismatch")
errForkIDRejected = errors.New("fork ID rejected")
// handshake errors
errNoStatusMsg = errors.New("no status message")
errNetworkIDMismatch = errors.New("network ID mismatch")
errGenesisMismatch = errors.New("genesis mismatch")
errForkIDRejected = errors.New("fork ID rejected")
errInvalidBlockRange = errors.New("invalid block range in status")
)
// Packet represents a p2p message in the `eth` protocol.
@ -82,7 +85,7 @@ type Packet interface {
}
// StatusPacket is the network packet for the status message.
type StatusPacket struct {
type StatusPacket68 struct {
ProtocolVersion uint32
NetworkID uint64
TD *big.Int
@ -91,6 +94,18 @@ type StatusPacket struct {
ForkID forkid.ID
}
// StatusPacket69 is the network packet for the status message.
type StatusPacket69 struct {
ProtocolVersion uint32
NetworkID uint64
Genesis common.Hash
ForkID forkid.ID
// initial available block range
EarliestBlock uint64
LatestBlock uint64
LatestBlockHash common.Hash
}
// NewBlockHashesPacket is the network packet for the block announcements.
type NewBlockHashesPacket []struct {
Hash common.Hash // Hash of one particular block being announced
@ -250,13 +265,21 @@ type GetReceiptsPacket struct {
}
// ReceiptsResponse is the network packet for block receipts distribution.
type ReceiptsResponse [][]*types.Receipt
type ReceiptsResponse []types.Receipts
// ReceiptsList is a type constraint for block receceipt list types.
type ReceiptsList interface {
*ReceiptList68 | *ReceiptList69
setBuffers(*receiptListBuffers)
EncodeForStorage() rlp.RawValue
types.DerivableList
}
// ReceiptsPacket is the network packet for block receipts distribution with
// request ID wrapping.
type ReceiptsPacket struct {
type ReceiptsPacket[L ReceiptsList] struct {
RequestId uint64
ReceiptsResponse
List []L
}
// ReceiptsRLPResponse is used for receipts, when we already have it encoded
@ -304,8 +327,18 @@ type PooledTransactionsRLPPacket struct {
PooledTransactionsRLPResponse
}
func (*StatusPacket) Name() string { return "Status" }
func (*StatusPacket) Kind() byte { return StatusMsg }
// BlockRangeUpdatePacket is an announcement of the node's available block range.
type BlockRangeUpdatePacket struct {
EarliestBlock uint64
LatestBlock uint64
LatestBlockHash common.Hash
}
func (*StatusPacket68) Name() string { return "Status" }
func (*StatusPacket68) Kind() byte { return StatusMsg }
func (*StatusPacket69) Name() string { return "Status" }
func (*StatusPacket69) Kind() byte { return StatusMsg }
func (*NewBlockHashesPacket) Name() string { return "NewBlockHashes" }
func (*NewBlockHashesPacket) Kind() byte { return NewBlockHashesMsg }
@ -342,3 +375,9 @@ func (*GetReceiptsRequest) Kind() byte { return GetReceiptsMsg }
func (*ReceiptsResponse) Name() string { return "Receipts" }
func (*ReceiptsResponse) Kind() byte { return ReceiptsMsg }
func (*ReceiptsRLPResponse) Name() string { return "Receipts" }
func (*ReceiptsRLPResponse) Kind() byte { return ReceiptsMsg }
func (*BlockRangeUpdatePacket) Name() string { return "BlockRangeUpdate" }
func (*BlockRangeUpdatePacket) Kind() byte { return BlockRangeUpdateMsg }

View file

@ -75,7 +75,7 @@ func TestEmptyMessages(t *testing.T) {
// All empty messages encodes to the same format
want := common.FromHex("c4820457c0")
for i, msg := range []interface{}{
for i, msg := range []any{
// Headers
GetBlockHeadersPacket{1111, nil},
BlockHeadersPacket{1111, nil},
@ -85,7 +85,6 @@ func TestEmptyMessages(t *testing.T) {
BlockBodiesRLPPacket{1111, nil},
// Receipts
GetReceiptsPacket{1111, nil},
ReceiptsPacket{1111, nil},
// Transactions
GetPooledTransactionsPacket{1111, nil},
PooledTransactionsPacket{1111, nil},
@ -99,7 +98,8 @@ func TestEmptyMessages(t *testing.T) {
BlockBodiesRLPPacket{1111, BlockBodiesRLPResponse([]rlp.RawValue{})},
// Receipts
GetReceiptsPacket{1111, GetReceiptsRequest([]common.Hash{})},
ReceiptsPacket{1111, ReceiptsResponse([][]*types.Receipt{})},
ReceiptsPacket[*ReceiptList68]{1111, []*ReceiptList68{}},
ReceiptsPacket[*ReceiptList69]{1111, []*ReceiptList69{}},
// Transactions
GetPooledTransactionsPacket{1111, GetPooledTransactionsRequest([]common.Hash{})},
PooledTransactionsPacket{1111, PooledTransactionsResponse([]*types.Transaction{})},
@ -168,7 +168,7 @@ func TestMessages(t *testing.T) {
receipts = []*types.Receipt{
{
Status: types.ReceiptStatusFailed,
CumulativeGasUsed: 1,
CumulativeGasUsed: 333,
Logs: []*types.Log{
{
Address: common.BytesToAddress([]byte{0x11}),
@ -176,11 +176,21 @@ func TestMessages(t *testing.T) {
Data: []byte{0x01, 0x00, 0xff},
},
},
TxHash: hashes[0],
ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
GasUsed: 111111,
},
{
Status: types.ReceiptStatusSuccessful,
CumulativeGasUsed: 444,
Logs: []*types.Log{
{
Address: common.BytesToAddress([]byte{0x22}),
Topics: []common.Hash{common.HexToHash("05668"), common.HexToHash("9773")},
Data: []byte{0x02, 0x0f, 0x0f, 0x0f, 0x06, 0x08},
},
},
},
}
miniDeriveFields(receipts[0], 0)
miniDeriveFields(receipts[1], 1)
rlpData, err := rlp.EncodeToBytes(receipts)
if err != nil {
t.Fatal(err)
@ -221,12 +231,17 @@ func TestMessages(t *testing.T) {
common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"),
},
{
ReceiptsPacket{1111, ReceiptsResponse([][]*types.Receipt{receipts})},
common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"),
ReceiptsPacket[*ReceiptList68]{1111, []*ReceiptList68{NewReceiptList68(receipts)}},
common.FromHex("f902e6820457f902e0f902ddf901688082014db9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000004000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ffb9016f01f9016b018201bcb9010000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000001000000000000000000000000000000000000000000000000040000000000000000000000000004000000000000000000000000000000000000000000000000000000008000400000000000000000000000000000000000000000000000000000000000000000000000000000040f862f860940000000000000000000000000000000000000022f842a00000000000000000000000000000000000000000000000000000000000005668a0000000000000000000000000000000000000000000000000000000000000977386020f0f0f0608"),
},
{
// Identical to the eth/68 encoding above.
ReceiptsRLPPacket{1111, ReceiptsRLPResponse([]rlp.RawValue{receiptsRlp})},
common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"),
common.FromHex("f902e6820457f902e0f902ddf901688082014db9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000004000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ffb9016f01f9016b018201bcb9010000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000001000000000000000000000000000000000000000000000000040000000000000000000000000004000000000000000000000000000000000000000000000000000000008000400000000000000000000000000000000000000000000000000000000000000000000000000000040f862f860940000000000000000000000000000000000000022f842a00000000000000000000000000000000000000000000000000000000000005668a0000000000000000000000000000000000000000000000000000000000000977386020f0f0f0608"),
},
{
ReceiptsPacket[*ReceiptList69]{1111, []*ReceiptList69{NewReceiptList69(receipts)}},
common.FromHex("f8da820457f8d5f8d3f866808082014df85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff86901018201bcf862f860940000000000000000000000000000000000000022f842a00000000000000000000000000000000000000000000000000000000000005668a0000000000000000000000000000000000000000000000000000000000000977386020f0f0f0608"),
},
{
GetPooledTransactionsPacket{1111, GetPooledTransactionsRequest(hashes)},

View file

@ -0,0 +1,462 @@
// Copyright 2024 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 eth
import (
"bytes"
"fmt"
"io"
"iter"
"math/big"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
)
// This is just a sanity limit for the size of a single receipt.
const maxReceiptSize = 16 * 1024 * 1024
// Receipt is the representation of receipts for networking purposes.
type Receipt struct {
TxType byte
PostStateOrStatus []byte
GasUsed uint64
Logs rlp.RawValue
}
func newReceipt(tr *types.Receipt) Receipt {
r := Receipt{TxType: tr.Type, GasUsed: tr.CumulativeGasUsed}
if tr.PostState != nil {
r.PostStateOrStatus = tr.PostState
} else {
r.PostStateOrStatus = new(big.Int).SetUint64(tr.Status).Bytes()
}
r.Logs, _ = rlp.EncodeToBytes(tr.Logs)
return r
}
// decode68 parses a receipt in the eth/68 network encoding.
func (r *Receipt) decode68(buf *receiptListBuffers, s *rlp.Stream) error {
k, size, err := s.Kind()
if err != nil {
return err
}
*r = Receipt{}
if k == rlp.List {
// Legacy receipt.
return r.decodeInnerList(s, false, true)
}
// Typed receipt.
if size < 2 || size > maxReceiptSize {
return fmt.Errorf("invalid receipt size %d", size)
}
buf.tmp.Reset()
buf.tmp.Grow(int(size))
payload := buf.tmp.Bytes()[:int(size)]
if err := s.ReadBytes(payload); err != nil {
return err
}
r.TxType = payload[0]
s2 := rlp.NewStream(bytes.NewReader(payload[1:]), 0)
return r.decodeInnerList(s2, false, true)
}
// decode69 parses a receipt in the eth/69 network encoding.
func (r *Receipt) decode69(s *rlp.Stream) error {
*r = Receipt{}
return r.decodeInnerList(s, true, false)
}
// decodeDatabase parses a receipt in the basic database encoding.
func (r *Receipt) decodeDatabase(txType byte, s *rlp.Stream) error {
*r = Receipt{TxType: txType}
return r.decodeInnerList(s, false, false)
}
func (r *Receipt) decodeInnerList(s *rlp.Stream, readTxType, readBloom bool) error {
_, err := s.List()
if err != nil {
return err
}
if readTxType {
r.TxType, err = s.Uint8()
if err != nil {
return fmt.Errorf("invalid txType: %w", err)
}
}
r.PostStateOrStatus, err = s.Bytes()
if err != nil {
return fmt.Errorf("invalid postStateOrStatus: %w", err)
}
r.GasUsed, err = s.Uint64()
if err != nil {
return fmt.Errorf("invalid gasUsed: %w", err)
}
if readBloom {
var b types.Bloom
if err := s.ReadBytes(b[:]); err != nil {
return fmt.Errorf("invalid bloom: %v", err)
}
}
r.Logs, err = s.Raw()
if err != nil {
return fmt.Errorf("invalid logs: %w", err)
}
return s.ListEnd()
}
// encodeForStorage produces the the storage encoding, i.e. the result matches
// the RLP encoding of types.ReceiptForStorage.
func (r *Receipt) encodeForStorage(w *rlp.EncoderBuffer) {
list := w.List()
w.WriteBytes(r.PostStateOrStatus)
w.WriteUint64(r.GasUsed)
w.Write(r.Logs)
w.ListEnd(list)
}
// encodeForNetwork68 produces the eth/68 network protocol encoding of a receipt.
// Note this recomputes the bloom filter of the receipt.
func (r *Receipt) encodeForNetwork68(buf *receiptListBuffers, w *rlp.EncoderBuffer) {
writeInner := func(w *rlp.EncoderBuffer) {
list := w.List()
w.WriteBytes(r.PostStateOrStatus)
w.WriteUint64(r.GasUsed)
bloom := r.bloom(&buf.bloom)
w.WriteBytes(bloom[:])
w.Write(r.Logs)
w.ListEnd(list)
}
if r.TxType == 0 {
writeInner(w)
} else {
buf.tmp.Reset()
buf.tmp.WriteByte(r.TxType)
buf.enc.Reset(&buf.tmp)
writeInner(&buf.enc)
buf.enc.Flush()
w.WriteBytes(buf.tmp.Bytes())
}
}
// encodeForNetwork69 produces the eth/69 network protocol encoding of a receipt.
func (r *Receipt) encodeForNetwork69(w *rlp.EncoderBuffer) {
list := w.List()
w.WriteUint64(uint64(r.TxType))
w.WriteBytes(r.PostStateOrStatus)
w.WriteUint64(r.GasUsed)
w.Write(r.Logs)
w.ListEnd(list)
}
// encodeForHash encodes a receipt for the block receiptsRoot derivation.
func (r *Receipt) encodeForHash(buf *receiptListBuffers, out *bytes.Buffer) {
// For typed receipts, add the tx type.
if r.TxType != 0 {
out.WriteByte(r.TxType)
}
// Encode list = [postStateOrStatus, gasUsed, bloom, logs].
w := &buf.enc
w.Reset(out)
l := w.List()
w.WriteBytes(r.PostStateOrStatus)
w.WriteUint64(r.GasUsed)
bloom := r.bloom(&buf.bloom)
w.WriteBytes(bloom[:])
w.Write(r.Logs)
w.ListEnd(l)
w.Flush()
}
// bloom computes the bloom filter of the receipt.
// Note this doesn't check the validity of encoding, and will produce an invalid filter
// for invalid input. This is acceptable for the purpose of this function, which is
// recomputing the receipt hash.
func (r *Receipt) bloom(buffer *[6]byte) types.Bloom {
var b types.Bloom
logsIter, err := rlp.NewListIterator(r.Logs)
if err != nil {
return b
}
for logsIter.Next() {
log, _, _ := rlp.SplitList(logsIter.Value())
address, log, _ := rlp.SplitString(log)
b.AddWithBuffer(address, buffer)
topicsIter, err := rlp.NewListIterator(log)
if err != nil {
return b
}
for topicsIter.Next() {
topic, _, _ := rlp.SplitString(topicsIter.Value())
b.AddWithBuffer(topic, buffer)
}
}
return b
}
type receiptListBuffers struct {
enc rlp.EncoderBuffer
bloom [6]byte
tmp bytes.Buffer
}
func initBuffers(buf **receiptListBuffers) {
if *buf == nil {
*buf = new(receiptListBuffers)
}
}
// encodeForStorage encodes a list of receipts for the database.
func (buf *receiptListBuffers) encodeForStorage(rs []Receipt) rlp.RawValue {
var out bytes.Buffer
w := &buf.enc
w.Reset(&out)
outer := w.List()
for _, receipts := range rs {
receipts.encodeForStorage(w)
}
w.ListEnd(outer)
w.Flush()
return out.Bytes()
}
// ReceiptList68 is a block receipt list as downloaded by eth/68.
// This also implements types.DerivableList for validation purposes.
type ReceiptList68 struct {
buf *receiptListBuffers
items []Receipt
}
// NewReceiptList68 creates a receipt list.
// This is slow, and exists for testing purposes.
func NewReceiptList68(trs []*types.Receipt) *ReceiptList68 {
rl := &ReceiptList68{items: make([]Receipt, len(trs))}
for i, tr := range trs {
rl.items[i] = newReceipt(tr)
}
return rl
}
func blockReceiptsToNetwork68(blockReceipts, blockBody rlp.RawValue) ([]byte, error) {
txTypesIter, err := txTypesInBody(blockBody)
if err != nil {
return nil, fmt.Errorf("invalid block body: %v", err)
}
nextTxType, stopTxTypes := iter.Pull(txTypesIter)
defer stopTxTypes()
var (
out bytes.Buffer
buf receiptListBuffers
)
blockReceiptIter, _ := rlp.NewListIterator(blockReceipts)
innerReader := bytes.NewReader(nil)
innerStream := rlp.NewStream(innerReader, 0)
w := rlp.NewEncoderBuffer(&out)
outer := w.List()
for i := 0; blockReceiptIter.Next(); i++ {
content := blockReceiptIter.Value()
innerReader.Reset(content)
innerStream.Reset(innerReader, uint64(len(content)))
var r Receipt
txType, _ := nextTxType()
if err := r.decodeDatabase(txType, innerStream); err != nil {
return nil, fmt.Errorf("invalid database receipt %d: %v", i, err)
}
r.encodeForNetwork68(&buf, &w)
}
w.ListEnd(outer)
w.Flush()
return out.Bytes(), nil
}
// setBuffers implements ReceiptsList.
func (rl *ReceiptList68) setBuffers(buf *receiptListBuffers) {
rl.buf = buf
}
// EncodeForStorage encodes the receipts for storage into the database.
func (rl *ReceiptList68) EncodeForStorage() rlp.RawValue {
initBuffers(&rl.buf)
return rl.buf.encodeForStorage(rl.items)
}
// Len implements types.DerivableList.
func (rl *ReceiptList68) Len() int {
return len(rl.items)
}
// EncodeIndex implements types.DerivableList.
func (rl *ReceiptList68) EncodeIndex(i int, out *bytes.Buffer) {
initBuffers(&rl.buf)
rl.items[i].encodeForHash(rl.buf, out)
}
// DecodeRLP decodes a list of receipts from the network format.
func (rl *ReceiptList68) DecodeRLP(s *rlp.Stream) error {
initBuffers(&rl.buf)
if _, err := s.List(); err != nil {
return err
}
for i := 0; s.MoreDataInList(); i++ {
var item Receipt
err := item.decode68(rl.buf, s)
if err != nil {
return fmt.Errorf("receipt %d: %v", i, err)
}
rl.items = append(rl.items, item)
}
return s.ListEnd()
}
// EncodeRLP encodes the list into the network format of eth/68.
func (rl *ReceiptList68) EncodeRLP(_w io.Writer) error {
initBuffers(&rl.buf)
w := rlp.NewEncoderBuffer(_w)
outer := w.List()
for i := range rl.items {
rl.items[i].encodeForNetwork68(rl.buf, &w)
}
w.ListEnd(outer)
return w.Flush()
}
// ReceiptList69 is the block receipt list as downloaded by eth/69.
// This implements types.DerivableList for validation purposes.
type ReceiptList69 struct {
buf *receiptListBuffers
items []Receipt
}
// NewReceiptList69 creates a receipt list.
// This is slow, and exists for testing purposes.
func NewReceiptList69(trs []*types.Receipt) *ReceiptList69 {
rl := &ReceiptList69{items: make([]Receipt, len(trs))}
for i, tr := range trs {
rl.items[i] = newReceipt(tr)
}
return rl
}
// setBuffers implements ReceiptsList.
func (rl *ReceiptList69) setBuffers(buf *receiptListBuffers) {
rl.buf = buf
}
// EncodeForStorage encodes the receipts for storage into the database.
func (rl *ReceiptList69) EncodeForStorage() rlp.RawValue {
initBuffers(&rl.buf)
return rl.buf.encodeForStorage(rl.items)
}
// Len implements types.DerivableList.
func (rl *ReceiptList69) Len() int {
return len(rl.items)
}
// EncodeIndex implements types.DerivableList.
func (rl *ReceiptList69) EncodeIndex(i int, out *bytes.Buffer) {
initBuffers(&rl.buf)
rl.items[i].encodeForHash(rl.buf, out)
}
// DecodeRLP decodes a list receipts from the network format.
func (rl *ReceiptList69) DecodeRLP(s *rlp.Stream) error {
if _, err := s.List(); err != nil {
return err
}
for i := 0; s.MoreDataInList(); i++ {
var item Receipt
err := item.decode69(s)
if err != nil {
return fmt.Errorf("receipt %d: %v", i, err)
}
rl.items = append(rl.items, item)
}
return s.ListEnd()
}
// EncodeRLP encodes the list into the network format of eth/69.
func (rl *ReceiptList69) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
outer := w.List()
for i := range rl.items {
rl.items[i].encodeForNetwork69(&w)
}
w.ListEnd(outer)
return w.Flush()
}
// blockReceiptsToNetwork69 takes a slice of rlp-encoded receipts, and transactions,
// and applies the type-encoding on the receipts (for non-legacy receipts).
// e.g. for non-legacy receipts: receipt-data -> {tx-type || receipt-data}
func blockReceiptsToNetwork69(blockReceipts, blockBody rlp.RawValue) ([]byte, error) {
txTypesIter, err := txTypesInBody(blockBody)
if err != nil {
return nil, fmt.Errorf("invalid block body: %v", err)
}
nextTxType, stopTxTypes := iter.Pull(txTypesIter)
defer stopTxTypes()
var (
out bytes.Buffer
enc = rlp.NewEncoderBuffer(&out)
it, _ = rlp.NewListIterator(blockReceipts)
)
outer := enc.List()
for i := 0; it.Next(); i++ {
txType, _ := nextTxType()
content, _, _ := rlp.SplitList(it.Value())
receiptList := enc.List()
enc.WriteUint64(uint64(txType))
enc.Write(content)
enc.ListEnd(receiptList)
}
enc.ListEnd(outer)
enc.Flush()
return out.Bytes(), nil
}
// txTypesInBody parses the transactions list of an encoded block body, returning just the types.
func txTypesInBody(body rlp.RawValue) (iter.Seq[byte], error) {
bodyFields, _, err := rlp.SplitList(body)
if err != nil {
return nil, err
}
txsIter, err := rlp.NewListIterator(bodyFields)
if err != nil {
return nil, err
}
return func(yield func(byte) bool) {
for txsIter.Next() {
var txType byte
switch k, content, _, _ := rlp.Split(txsIter.Value()); k {
case rlp.List:
txType = 0
case rlp.String:
if len(content) > 0 {
txType = content[0]
}
}
if !yield(txType) {
return
}
}
}, nil
}

View file

@ -0,0 +1,158 @@
// Copyright 2024 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 eth
import (
"bytes"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
// miniDeriveFields derives the necessary receipt fields to make types.DeriveSha work.
func miniDeriveFields(r *types.Receipt, txType byte) {
r.Type = txType
r.Bloom = types.CreateBloom(r)
}
var receiptsTestLogs1 = []*types.Log{{Address: common.Address{1}, Topics: []common.Hash{{1}}}}
var receiptsTestLogs2 = []*types.Log{
{Address: common.Address{2}, Topics: []common.Hash{{21}, {22}}, Data: []byte{2, 2, 32, 32}},
{Address: common.Address{3}, Topics: []common.Hash{{31}, {32}}, Data: []byte{3, 3, 32, 32}},
}
var receiptsTests = []struct {
input []types.ReceiptForStorage
txs []*types.Transaction
root common.Hash
}{
{
input: []types.ReceiptForStorage{{CumulativeGasUsed: 555, Status: 1, Logs: nil}},
txs: []*types.Transaction{types.NewTx(&types.LegacyTx{})},
},
{
input: []types.ReceiptForStorage{{CumulativeGasUsed: 555, Status: 1, Logs: nil}},
txs: []*types.Transaction{types.NewTx(&types.DynamicFeeTx{})},
},
{
input: []types.ReceiptForStorage{{CumulativeGasUsed: 555, Status: 1, Logs: nil}},
txs: []*types.Transaction{types.NewTx(&types.AccessListTx{})},
},
{
input: []types.ReceiptForStorage{{CumulativeGasUsed: 555, Status: 1, Logs: receiptsTestLogs1}},
txs: []*types.Transaction{types.NewTx(&types.LegacyTx{})},
},
{
input: []types.ReceiptForStorage{{CumulativeGasUsed: 555, Status: 1, Logs: receiptsTestLogs2}},
txs: []*types.Transaction{types.NewTx(&types.AccessListTx{})},
},
}
func init() {
for i := range receiptsTests {
// derive basic fields
for j := range receiptsTests[i].input {
r := (*types.Receipt)(&receiptsTests[i].input[j])
txType := receiptsTests[i].txs[j].Type()
miniDeriveFields(r, txType)
}
// compute expected root
receipts := make(types.Receipts, len(receiptsTests[i].input))
for j, sr := range receiptsTests[i].input {
r := types.Receipt(sr)
receipts[j] = &r
}
receiptsTests[i].root = types.DeriveSha(receipts, trie.NewStackTrie(nil))
}
}
func TestReceiptList69(t *testing.T) {
for i, test := range receiptsTests {
// encode receipts from types.ReceiptForStorage object.
canonDB, _ := rlp.EncodeToBytes(test.input)
// encode block body from types object.
blockBody := types.Body{Transactions: test.txs}
canonBody, _ := rlp.EncodeToBytes(blockBody)
// convert from storage encoding to network encoding
network, err := blockReceiptsToNetwork69(canonDB, canonBody)
if err != nil {
t.Fatalf("test[%d]: blockReceiptsToNetwork69 error: %v", i, err)
}
// parse as Receipts response list from network encoding
var rl ReceiptList69
if err := rlp.DecodeBytes(network, &rl); err != nil {
t.Fatalf("test[%d]: can't decode network receipts: %v", i, err)
}
rlStorageEnc := rl.EncodeForStorage()
if !bytes.Equal(rlStorageEnc, canonDB) {
t.Fatalf("test[%d]: re-encoded receipts not equal\nhave: %x\nwant: %x", i, rlStorageEnc, canonDB)
}
rlNetworkEnc, _ := rlp.EncodeToBytes(&rl)
if !bytes.Equal(rlNetworkEnc, network) {
t.Fatalf("test[%d]: re-encoded network receipt list not equal\nhave: %x\nwant: %x", i, rlNetworkEnc, network)
}
// compute root hash from ReceiptList69 and compare.
responseHash := types.DeriveSha(&rl, trie.NewStackTrie(nil))
if responseHash != test.root {
t.Fatalf("test[%d]: wrong root hash from ReceiptList69\nhave: %v\nwant: %v", i, responseHash, test.root)
}
}
}
func TestReceiptList68(t *testing.T) {
for i, test := range receiptsTests {
// encode receipts from types.ReceiptForStorage object.
canonDB, _ := rlp.EncodeToBytes(test.input)
// encode block body from types object.
blockBody := types.Body{Transactions: test.txs}
canonBody, _ := rlp.EncodeToBytes(blockBody)
// convert from storage encoding to network encoding
network, err := blockReceiptsToNetwork68(canonDB, canonBody)
if err != nil {
t.Fatalf("test[%d]: blockReceiptsToNetwork68 error: %v", i, err)
}
// parse as Receipts response list from network encoding
var rl ReceiptList68
if err := rlp.DecodeBytes(network, &rl); err != nil {
t.Fatalf("test[%d]: can't decode network receipts: %v", i, err)
}
rlStorageEnc := rl.EncodeForStorage()
if !bytes.Equal(rlStorageEnc, canonDB) {
t.Fatalf("test[%d]: re-encoded receipts not equal\nhave: %x\nwant: %x", i, rlStorageEnc, canonDB)
}
rlNetworkEnc, _ := rlp.EncodeToBytes(&rl)
if !bytes.Equal(rlNetworkEnc, network) {
t.Fatalf("test[%d]: re-encoded network receipt list not equal\nhave: %x\nwant: %x", i, rlNetworkEnc, network)
}
// compute root hash from ReceiptList68 and compare.
responseHash := types.DeriveSha(&rl, trie.NewStackTrie(nil))
if responseHash != test.root {
t.Fatalf("test[%d]: wrong root hash from ReceiptList68\nhave: %v\nwant: %v", i, responseHash, test.root)
}
}
}

View file

@ -1,26 +0,0 @@
// Copyright 2021 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 eth
import (
"time"
"github.com/ethereum/go-ethereum/p2p/tracker"
)
// requestTracker is a singleton tracker for eth/66 and newer request times.
var requestTracker = tracker.New(ProtocolName, 5*time.Minute)

View file

@ -23,6 +23,8 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
@ -31,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb/database"
)
const (
@ -279,7 +282,16 @@ func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePac
if err != nil {
return nil, nil
}
it, err := chain.Snapshots().AccountIterator(req.Root, req.Origin)
// Temporary solution: using the snapshot interface for both cases.
// This can be removed once the hash scheme is deprecated.
var it snapshot.AccountIterator
if chain.TrieDB().Scheme() == rawdb.HashScheme {
// The snapshot is assumed to be available in hash mode if
// the SNAP protocol is enabled.
it, err = chain.Snapshots().AccountIterator(req.Root, req.Origin)
} else {
it, err = chain.TrieDB().AccountIterator(req.Root, req.Origin)
}
if err != nil {
return nil, nil
}
@ -359,7 +371,19 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP
limit, req.Limit = common.BytesToHash(req.Limit), nil
}
// Retrieve the requested state and bail out if non existent
it, err := chain.Snapshots().StorageIterator(req.Root, account, origin)
var (
err error
it snapshot.StorageIterator
)
// Temporary solution: using the snapshot interface for both cases.
// This can be removed once the hash scheme is deprecated.
if chain.TrieDB().Scheme() == rawdb.HashScheme {
// The snapshot is assumed to be available in hash mode if
// the SNAP protocol is enabled.
it, err = chain.Snapshots().StorageIterator(req.Root, account, origin)
} else {
it, err = chain.TrieDB().StorageIterator(req.Root, account, origin)
}
if err != nil {
return nil, nil
}
@ -479,8 +503,15 @@ func ServiceGetTrieNodesQuery(chain *core.BlockChain, req *GetTrieNodesPacket, s
// We don't have the requested state available, bail out
return nil, nil
}
// The 'snap' might be nil, in which case we cannot serve storage slots.
snap := chain.Snapshots().Snapshot(req.Root)
// The 'reader' might be nil, in which case we cannot serve storage slots
// via snapshot.
var reader database.StateReader
if chain.Snapshots() != nil {
reader = chain.Snapshots().Snapshot(req.Root)
}
if reader == nil {
reader, _ = triedb.StateReader(req.Root)
}
// Retrieve trie nodes until the packet size limit is reached
var (
nodes [][]byte
@ -505,8 +536,9 @@ func ServiceGetTrieNodesQuery(chain *core.BlockChain, req *GetTrieNodesPacket, s
default:
var stRoot common.Hash
// Storage slots requested, open the storage trie and retrieve from there
if snap == nil {
if reader == nil {
// We don't have the requested state snapshotted yet (or it is stale),
// but can look up the account via the trie instead.
account, err := accTrie.GetAccountByHash(common.BytesToHash(pathset[0]))
@ -516,7 +548,7 @@ func ServiceGetTrieNodesQuery(chain *core.BlockChain, req *GetTrieNodesPacket, s
}
stRoot = account.Root
} else {
account, err := snap.Account(common.BytesToHash(pathset[0]))
account, err := reader.Account(common.BytesToHash(pathset[0]))
loads++ // always account database reads, even for failures
if err != nil || account == nil {
break

View file

@ -1962,5 +1962,5 @@ func newDbConfig(scheme string) *triedb.Config {
if scheme == rawdb.HashScheme {
return &triedb.Config{}
}
return &triedb.Config{PathDB: pathdb.Defaults}
return &triedb.Config{PathDB: &pathdb.Config{SnapshotNoBuild: true}}
}

View file

@ -0,0 +1,144 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package tracetest
import (
"encoding/json"
"math/big"
"os"
"path/filepath"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"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/eth/tracers"
"github.com/ethereum/go-ethereum/tests"
"github.com/stretchr/testify/require"
)
type accessedSlots struct {
Reads map[string][]string `json:"reads"`
Writes map[string]uint64 `json:"writes"`
TransientReads map[string]uint64 `json:"transientReads"`
TransientWrites map[string]uint64 `json:"transientWrites"`
}
type contractSizeWithOpcode struct {
ContractSize int `json:"contractSize"`
Opcode vm.OpCode `json:"opcode"`
}
// erc7562Trace is the result of a erc7562Tracer run.
type erc7562Trace struct {
From common.Address `json:"from"`
Gas *hexutil.Uint64 `json:"gas"`
GasUsed *hexutil.Uint64 `json:"gasUsed"`
To *common.Address `json:"to,omitempty" rlp:"optional"`
Input hexutil.Bytes `json:"input" rlp:"optional"`
Output hexutil.Bytes `json:"output,omitempty" rlp:"optional"`
Error string `json:"error,omitempty" rlp:"optional"`
RevertReason string `json:"revertReason,omitempty"`
Logs []callLog `json:"logs,omitempty" rlp:"optional"`
Value *hexutil.Big `json:"value,omitempty" rlp:"optional"`
AccessedSlots accessedSlots `json:"accessedSlots"`
ExtCodeAccessInfo []common.Address `json:"extCodeAccessInfo"`
UsedOpcodes map[hexutil.Uint64]uint64 `json:"usedOpcodes"`
ContractSize map[common.Address]*contractSizeWithOpcode `json:"contractSize"`
OutOfGas bool `json:"outOfGas"`
Calls []erc7562Trace `json:"calls,omitempty" rlp:"optional"`
Keccak []hexutil.Bytes `json:"keccak,omitempty"`
Type string `json:"type"`
}
// erc7562TracerTest defines a single test to check the erc7562 tracer against.
type erc7562TracerTest struct {
tracerTestEnv
Result *erc7562Trace `json:"result"`
}
func TestErc7562Tracer(t *testing.T) {
dirPath := "erc7562_tracer"
tracerName := "erc7562Tracer"
files, err := os.ReadDir(filepath.Join("testdata", dirPath))
if err != nil {
t.Fatalf("failed to retrieve tracer test suite: %v", err)
}
for _, file := range files {
if !strings.HasSuffix(file.Name(), ".json") {
continue
}
t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
t.Parallel()
var (
test = new(erc7562TracerTest)
tx = new(types.Transaction)
)
// erc7562 tracer test found, read if from disk
if blob, err := os.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil {
t.Fatalf("failed to read testcase: %v", err)
} else if err := json.Unmarshal(blob, test); err != nil {
t.Fatalf("failed to parse testcase: %v", err)
}
if err := tx.UnmarshalBinary(common.FromHex(test.Input)); err != nil {
t.Fatalf("failed to parse testcase input: %v", err)
}
// Configure a blockchain with the given prestate
var (
signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time))
context = test.Context.toBlockContext(test.Genesis)
st = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme)
)
st.Close()
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig, test.Genesis.Config)
if err != nil {
t.Fatalf("failed to create erc7562 tracer: %v", err)
}
logState := vm.StateDB(st.StateDB)
if tracer.Hooks != nil {
logState = state.NewHookedState(st.StateDB, tracer.Hooks)
}
msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err)
}
evm := vm.NewEVM(context, logState, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if err != nil {
t.Fatalf("failed to execute transaction: %v", err)
}
tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
// Retrieve the trace result and compare against the expected.
res, err := tracer.GetResult()
if err != nil {
t.Fatalf("failed to retrieve trace result: %v", err)
}
want, err := json.Marshal(test.Result)
if err != nil {
t.Fatalf("failed to marshal test: %v", err)
}
require.JSONEq(t, string(res), string(want))
})
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,530 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package native
import (
"bytes"
"encoding/json"
"errors"
"math/big"
"slices"
"sync/atomic"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/internal"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
//go:generate go run github.com/fjl/gencodec -type callFrameWithOpcodes -field-override callFrameWithOpcodesMarshaling -out gen_callframewithopcodes_json.go
func init() {
tracers.DefaultDirectory.Register("erc7562Tracer", newErc7562Tracer, false)
}
type contractSizeWithOpcode struct {
ContractSize int `json:"contractSize"`
Opcode vm.OpCode `json:"opcode"`
}
type callFrameWithOpcodes struct {
Type vm.OpCode `json:"-"`
From common.Address `json:"from"`
Gas uint64 `json:"gas"`
GasUsed uint64 `json:"gasUsed"`
To *common.Address `json:"to,omitempty" rlp:"optional"`
Input []byte `json:"input" rlp:"optional"`
Output []byte `json:"output,omitempty" rlp:"optional"`
Error string `json:"error,omitempty" rlp:"optional"`
RevertReason string `json:"revertReason,omitempty"`
Logs []callLog `json:"logs,omitempty" rlp:"optional"`
Value *big.Int `json:"value,omitempty" rlp:"optional"`
revertedSnapshot bool
AccessedSlots accessedSlots `json:"accessedSlots"`
ExtCodeAccessInfo []common.Address `json:"extCodeAccessInfo"`
UsedOpcodes map[vm.OpCode]uint64 `json:"usedOpcodes"`
ContractSize map[common.Address]*contractSizeWithOpcode `json:"contractSize"`
OutOfGas bool `json:"outOfGas"`
// Keccak preimages for the whole transaction are stored in the
// root call frame.
KeccakPreimages [][]byte `json:"keccak,omitempty"`
Calls []callFrameWithOpcodes `json:"calls,omitempty" rlp:"optional"`
}
func (f callFrameWithOpcodes) TypeString() string {
return f.Type.String()
}
func (f callFrameWithOpcodes) failed() bool {
return len(f.Error) > 0 && f.revertedSnapshot
}
func (f *callFrameWithOpcodes) processOutput(output []byte, err error, reverted bool) {
output = common.CopyBytes(output)
// Clear error if tx wasn't reverted. This happened
// for pre-homestead contract storage OOG.
if err != nil && !reverted {
err = nil
}
if err == nil {
f.Output = output
return
}
f.Error = err.Error()
f.revertedSnapshot = reverted
if f.Type == vm.CREATE || f.Type == vm.CREATE2 {
f.To = nil
}
if !errors.Is(err, vm.ErrExecutionReverted) || len(output) == 0 {
return
}
f.Output = output
if len(output) < 4 {
return
}
if unpacked, err := abi.UnpackRevert(output); err == nil {
f.RevertReason = unpacked
}
}
type callFrameWithOpcodesMarshaling struct {
TypeString string `json:"type"`
Gas hexutil.Uint64
GasUsed hexutil.Uint64
Value *hexutil.Big
Input hexutil.Bytes
Output hexutil.Bytes
UsedOpcodes map[hexutil.Uint64]uint64
KeccakPreimages []hexutil.Bytes
}
type accessedSlots struct {
Reads map[common.Hash][]common.Hash `json:"reads"`
Writes map[common.Hash]uint64 `json:"writes"`
TransientReads map[common.Hash]uint64 `json:"transientReads"`
TransientWrites map[common.Hash]uint64 `json:"transientWrites"`
}
type opcodeWithPartialStack struct {
Opcode vm.OpCode
StackTopItems []uint256.Int
}
type erc7562Tracer struct {
config erc7562TracerConfig
gasLimit uint64
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
env *tracing.VMContext
ignoredOpcodes map[vm.OpCode]struct{}
callstackWithOpcodes []callFrameWithOpcodes
lastOpWithStack *opcodeWithPartialStack
keccakPreimages map[string]struct{}
}
// newErc7562Tracer returns a native go tracer which tracks
// call frames of a tx, and implements vm.EVMLogger.
func newErc7562Tracer(ctx *tracers.Context, cfg json.RawMessage, _ *params.ChainConfig) (*tracers.Tracer, error) {
t, err := newErc7562TracerObject(cfg)
if err != nil {
return nil, err
}
return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnOpcode: t.OnOpcode,
OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnLog: t.OnLog,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil
}
type erc7562TracerConfig struct {
StackTopItemsSize int `json:"stackTopItemsSize"`
IgnoredOpcodes []hexutil.Uint64 `json:"ignoredOpcodes"` // Opcodes to ignore during OnOpcode hook execution
WithLog bool `json:"withLog"` // If true, erc7562 tracer will collect event logs
}
func getFullConfiguration(partial erc7562TracerConfig) erc7562TracerConfig {
config := partial
if config.IgnoredOpcodes == nil {
config.IgnoredOpcodes = defaultIgnoredOpcodes()
}
if config.StackTopItemsSize == 0 {
config.StackTopItemsSize = 3
}
return config
}
func newErc7562TracerObject(cfg json.RawMessage) (*erc7562Tracer, error) {
var config erc7562TracerConfig
if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil {
return nil, err
}
}
fullConfig := getFullConfiguration(config)
// Create a map of ignored opcodes for fast lookup
ignoredOpcodes := make(map[vm.OpCode]struct{}, len(fullConfig.IgnoredOpcodes))
for _, op := range fullConfig.IgnoredOpcodes {
ignoredOpcodes[vm.OpCode(op)] = struct{}{}
}
// First callframe contains tx context info
// and is populated on start and end.
return &erc7562Tracer{
callstackWithOpcodes: make([]callFrameWithOpcodes, 0, 1),
config: fullConfig,
keccakPreimages: make(map[string]struct{}),
ignoredOpcodes: ignoredOpcodes,
}, nil
}
func (t *erc7562Tracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
t.env = env
t.gasLimit = tx.Gas()
}
// OnEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *erc7562Tracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
// Skip if tracing was interrupted
if t.interrupt.Load() {
return
}
toCopy := to
call := callFrameWithOpcodes{
Type: vm.OpCode(typ),
From: from,
To: &toCopy,
Input: common.CopyBytes(input),
Gas: gas,
Value: value,
AccessedSlots: accessedSlots{
Reads: map[common.Hash][]common.Hash{},
Writes: map[common.Hash]uint64{},
TransientReads: map[common.Hash]uint64{},
TransientWrites: map[common.Hash]uint64{},
},
UsedOpcodes: map[vm.OpCode]uint64{},
ExtCodeAccessInfo: make([]common.Address, 0),
ContractSize: map[common.Address]*contractSizeWithOpcode{},
}
if depth == 0 {
call.Gas = t.gasLimit
}
t.callstackWithOpcodes = append(t.callstackWithOpcodes, call)
}
func (t *erc7562Tracer) captureEnd(output []byte, err error, reverted bool) {
if len(t.callstackWithOpcodes) != 1 {
return
}
t.callstackWithOpcodes[0].processOutput(output, err, reverted)
}
// OnExit is called when EVM exits a scope, even if the scope didn't
// execute any code.
func (t *erc7562Tracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
if t.interrupt.Load() {
return
}
if depth == 0 {
t.captureEnd(output, err, reverted)
return
}
size := len(t.callstackWithOpcodes)
if size <= 1 {
return
}
// Pop call.
call := t.callstackWithOpcodes[size-1]
t.callstackWithOpcodes = t.callstackWithOpcodes[:size-1]
size -= 1
if errors.Is(err, vm.ErrCodeStoreOutOfGas) || errors.Is(err, vm.ErrOutOfGas) {
call.OutOfGas = true
}
call.GasUsed = gasUsed
call.processOutput(output, err, reverted)
// Nest call into parent.
t.callstackWithOpcodes[size-1].Calls = append(t.callstackWithOpcodes[size-1].Calls, call)
}
func (t *erc7562Tracer) OnTxEnd(receipt *types.Receipt, err error) {
if t.interrupt.Load() {
return
}
// Error happened during tx validation.
if err != nil {
return
}
t.callstackWithOpcodes[0].GasUsed = receipt.GasUsed
if t.config.WithLog {
// Logs are not emitted when the call fails
t.clearFailedLogs(&t.callstackWithOpcodes[0], false)
}
}
func (t *erc7562Tracer) OnLog(log1 *types.Log) {
// Only logs need to be captured via opcode processing
if !t.config.WithLog {
return
}
// Skip if tracing was interrupted
if t.interrupt.Load() {
return
}
l := callLog{
Address: log1.Address,
Topics: log1.Topics,
Data: log1.Data,
Position: hexutil.Uint(len(t.callstackWithOpcodes[len(t.callstackWithOpcodes)-1].Calls)),
}
t.callstackWithOpcodes[len(t.callstackWithOpcodes)-1].Logs = append(t.callstackWithOpcodes[len(t.callstackWithOpcodes)-1].Logs, l)
}
// GetResult returns the json-encoded nested list of call traces, and any
// 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 len(t.callstackWithOpcodes) != 1 {
return nil, errors.New("incorrect number of top-level calls")
}
keccak := make([][]byte, 0, len(t.callstackWithOpcodes[0].KeccakPreimages))
for k := range t.keccakPreimages {
keccak = append(keccak, []byte(k))
}
t.callstackWithOpcodes[0].KeccakPreimages = keccak
slices.SortFunc(keccak, func(a, b []byte) int {
return bytes.Compare(a, b)
})
enc, err := json.Marshal(t.callstackWithOpcodes[0])
if err != nil {
return nil, err
}
return enc, t.reason
}
// Stop terminates execution of the tracer at the first opportune moment.
func (t *erc7562Tracer) Stop(err error) {
t.reason = err
t.interrupt.Store(true)
}
// clearFailedLogs clears the logs of a callframe and all its children
// in case of execution failure.
func (t *erc7562Tracer) clearFailedLogs(cf *callFrameWithOpcodes, parentFailed bool) {
failed := cf.failed() || parentFailed
// Clear own logs
if failed {
cf.Logs = nil
}
for i := range cf.Calls {
t.clearFailedLogs(&cf.Calls[i], failed)
}
}
func (t *erc7562Tracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
if t.interrupt.Load() {
return
}
var (
opcode = vm.OpCode(op)
opcodeWithStack *opcodeWithPartialStack
stackSize = len(scope.StackData())
stackLimit = min(stackSize, t.config.StackTopItemsSize)
stackTopItems = make([]uint256.Int, stackLimit)
)
for i := 0; i < stackLimit; i++ {
stackTopItems[i] = *peepStack(scope.StackData(), i)
}
opcodeWithStack = &opcodeWithPartialStack{
Opcode: opcode,
StackTopItems: stackTopItems,
}
t.handleReturnRevert(opcode)
size := len(t.callstackWithOpcodes)
currentCallFrame := &t.callstackWithOpcodes[size-1]
if t.lastOpWithStack != nil {
t.handleExtOpcodes(opcode, currentCallFrame)
}
t.handleAccessedContractSize(opcode, scope, currentCallFrame)
if t.lastOpWithStack != nil {
t.handleGasObserved(opcode, currentCallFrame)
}
t.storeUsedOpcode(opcode, currentCallFrame)
t.handleStorageAccess(opcode, scope, currentCallFrame)
t.storeKeccak(opcode, scope)
t.lastOpWithStack = opcodeWithStack
}
func (t *erc7562Tracer) handleReturnRevert(opcode vm.OpCode) {
if opcode == vm.REVERT || opcode == vm.RETURN {
t.lastOpWithStack = nil
}
}
func (t *erc7562Tracer) handleGasObserved(opcode vm.OpCode, currentCallFrame *callFrameWithOpcodes) {
// [OP-012]
pendingGasObserved := t.lastOpWithStack.Opcode == vm.GAS && !isCall(opcode)
if pendingGasObserved {
currentCallFrame.UsedOpcodes[vm.GAS]++
}
}
func (t *erc7562Tracer) storeUsedOpcode(opcode vm.OpCode, currentCallFrame *callFrameWithOpcodes) {
// ignore "unimportant" opcodes
if opcode != vm.GAS && !t.isIgnoredOpcode(opcode) {
currentCallFrame.UsedOpcodes[opcode]++
}
}
func (t *erc7562Tracer) handleStorageAccess(opcode vm.OpCode, scope tracing.OpContext, currentCallFrame *callFrameWithOpcodes) {
if opcode == vm.SLOAD || opcode == vm.SSTORE || opcode == vm.TLOAD || opcode == vm.TSTORE {
slot := common.BytesToHash(peepStack(scope.StackData(), 0).Bytes())
addr := scope.Address()
if opcode == vm.SLOAD {
// read slot values before this UserOp was created
// (so saving it if it was written before the first read)
_, rOk := currentCallFrame.AccessedSlots.Reads[slot]
_, wOk := currentCallFrame.AccessedSlots.Writes[slot]
if !rOk && !wOk {
currentCallFrame.AccessedSlots.Reads[slot] = append(currentCallFrame.AccessedSlots.Reads[slot], t.env.StateDB.GetState(addr, slot))
}
} else if opcode == vm.SSTORE {
currentCallFrame.AccessedSlots.Writes[slot]++
} else if opcode == vm.TLOAD {
currentCallFrame.AccessedSlots.TransientReads[slot]++
} else {
currentCallFrame.AccessedSlots.TransientWrites[slot]++
}
}
}
func (t *erc7562Tracer) storeKeccak(opcode vm.OpCode, scope tracing.OpContext) {
if opcode == vm.KECCAK256 {
dataOffset := peepStack(scope.StackData(), 0).Uint64()
dataLength := peepStack(scope.StackData(), 1).Uint64()
preimage, err := internal.GetMemoryCopyPadded(scope.MemoryData(), int64(dataOffset), int64(dataLength))
if err != nil {
log.Warn("erc7562Tracer: failed to copy keccak preimage from memory", "err", err)
return
}
t.keccakPreimages[string(preimage)] = struct{}{}
}
}
func (t *erc7562Tracer) handleExtOpcodes(opcode vm.OpCode, currentCallFrame *callFrameWithOpcodes) {
if isEXT(t.lastOpWithStack.Opcode) {
addr := common.HexToAddress(t.lastOpWithStack.StackTopItems[0].Hex())
// only store the last EXTCODE* opcode per address - could even be a boolean for our current use-case
// [OP-051]
if !(t.lastOpWithStack.Opcode == vm.EXTCODESIZE && opcode == vm.ISZERO) {
currentCallFrame.ExtCodeAccessInfo = append(currentCallFrame.ExtCodeAccessInfo, addr)
}
}
}
func (t *erc7562Tracer) handleAccessedContractSize(opcode vm.OpCode, scope tracing.OpContext, currentCallFrame *callFrameWithOpcodes) {
// [OP-041]
if isEXTorCALL(opcode) {
n := 0
if !isEXT(opcode) {
n = 1
}
addr := common.BytesToAddress(peepStack(scope.StackData(), n).Bytes())
if _, ok := currentCallFrame.ContractSize[addr]; !ok {
currentCallFrame.ContractSize[addr] = &contractSizeWithOpcode{
ContractSize: len(t.env.StateDB.GetCode(addr)),
Opcode: opcode,
}
}
}
}
func peepStack(stackData []uint256.Int, n int) *uint256.Int {
return &stackData[len(stackData)-n-1]
}
func isEXTorCALL(opcode vm.OpCode) bool {
return isEXT(opcode) || isCall(opcode)
}
func isEXT(opcode vm.OpCode) bool {
return opcode == vm.EXTCODEHASH ||
opcode == vm.EXTCODESIZE ||
opcode == vm.EXTCODECOPY
}
func isCall(opcode vm.OpCode) bool {
return opcode == vm.CALL ||
opcode == vm.CALLCODE ||
opcode == vm.DELEGATECALL ||
opcode == vm.STATICCALL
}
// Check if this opcode is ignored for the purposes of generating the used opcodes report
func (t *erc7562Tracer) isIgnoredOpcode(opcode vm.OpCode) bool {
if _, ok := t.ignoredOpcodes[opcode]; ok {
return true
}
return false
}
func defaultIgnoredOpcodes() []hexutil.Uint64 {
ignored := make([]hexutil.Uint64, 0, 64)
// Allow all PUSHx, DUPx and SWAPx opcodes as they have sequential codes
for op := vm.PUSH0; op < vm.SWAP16; op++ {
ignored = append(ignored, hexutil.Uint64(op))
}
for _, op := range []vm.OpCode{
vm.POP, vm.ADD, vm.SUB, vm.MUL,
vm.DIV, vm.EQ, vm.LT, vm.GT,
vm.SLT, vm.SGT, vm.SHL, vm.SHR,
vm.AND, vm.OR, vm.NOT, vm.ISZERO,
} {
ignored = append(ignored, hexutil.Uint64(op))
}
return ignored
}

View file

@ -0,0 +1,159 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package native
import (
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm"
)
var _ = (*callFrameWithOpcodesMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (c callFrameWithOpcodes) MarshalJSON() ([]byte, error) {
type callFrameWithOpcodes0 struct {
Type vm.OpCode `json:"-"`
From common.Address `json:"from"`
Gas hexutil.Uint64 `json:"gas"`
GasUsed hexutil.Uint64 `json:"gasUsed"`
To *common.Address `json:"to,omitempty" rlp:"optional"`
Input hexutil.Bytes `json:"input" rlp:"optional"`
Output hexutil.Bytes `json:"output,omitempty" rlp:"optional"`
Error string `json:"error,omitempty" rlp:"optional"`
RevertReason string `json:"revertReason,omitempty"`
Logs []callLog `json:"logs,omitempty" rlp:"optional"`
Value *hexutil.Big `json:"value,omitempty" rlp:"optional"`
AccessedSlots accessedSlots `json:"accessedSlots"`
ExtCodeAccessInfo []common.Address `json:"extCodeAccessInfo"`
UsedOpcodes map[hexutil.Uint64]uint64 `json:"usedOpcodes"`
ContractSize map[common.Address]*contractSizeWithOpcode `json:"contractSize"`
OutOfGas bool `json:"outOfGas"`
KeccakPreimages []hexutil.Bytes `json:"keccak,omitempty"`
Calls []callFrameWithOpcodes `json:"calls,omitempty" rlp:"optional"`
TypeString string `json:"type"`
}
var enc callFrameWithOpcodes0
enc.Type = c.Type
enc.From = c.From
enc.Gas = hexutil.Uint64(c.Gas)
enc.GasUsed = hexutil.Uint64(c.GasUsed)
enc.To = c.To
enc.Input = c.Input
enc.Output = c.Output
enc.Error = c.Error
enc.RevertReason = c.RevertReason
enc.Logs = c.Logs
enc.Value = (*hexutil.Big)(c.Value)
enc.AccessedSlots = c.AccessedSlots
enc.ExtCodeAccessInfo = c.ExtCodeAccessInfo
if c.UsedOpcodes != nil {
enc.UsedOpcodes = make(map[hexutil.Uint64]uint64, len(c.UsedOpcodes))
for k, v := range c.UsedOpcodes {
enc.UsedOpcodes[hexutil.Uint64(k)] = v
}
}
enc.ContractSize = c.ContractSize
enc.OutOfGas = c.OutOfGas
if c.KeccakPreimages != nil {
enc.KeccakPreimages = make([]hexutil.Bytes, len(c.KeccakPreimages))
for k, v := range c.KeccakPreimages {
enc.KeccakPreimages[k] = v
}
}
enc.Calls = c.Calls
enc.TypeString = c.TypeString()
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (c *callFrameWithOpcodes) UnmarshalJSON(input []byte) error {
type callFrameWithOpcodes0 struct {
Type *vm.OpCode `json:"-"`
From *common.Address `json:"from"`
Gas *hexutil.Uint64 `json:"gas"`
GasUsed *hexutil.Uint64 `json:"gasUsed"`
To *common.Address `json:"to,omitempty" rlp:"optional"`
Input *hexutil.Bytes `json:"input" rlp:"optional"`
Output *hexutil.Bytes `json:"output,omitempty" rlp:"optional"`
Error *string `json:"error,omitempty" rlp:"optional"`
RevertReason *string `json:"revertReason,omitempty"`
Logs []callLog `json:"logs,omitempty" rlp:"optional"`
Value *hexutil.Big `json:"value,omitempty" rlp:"optional"`
AccessedSlots *accessedSlots `json:"accessedSlots"`
ExtCodeAccessInfo []common.Address `json:"extCodeAccessInfo"`
UsedOpcodes map[hexutil.Uint64]uint64 `json:"usedOpcodes"`
ContractSize map[common.Address]*contractSizeWithOpcode `json:"contractSize"`
OutOfGas *bool `json:"outOfGas"`
KeccakPreimages []hexutil.Bytes `json:"keccak,omitempty"`
Calls []callFrameWithOpcodes `json:"calls,omitempty" rlp:"optional"`
}
var dec callFrameWithOpcodes0
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Type != nil {
c.Type = *dec.Type
}
if dec.From != nil {
c.From = *dec.From
}
if dec.Gas != nil {
c.Gas = uint64(*dec.Gas)
}
if dec.GasUsed != nil {
c.GasUsed = uint64(*dec.GasUsed)
}
if dec.To != nil {
c.To = dec.To
}
if dec.Input != nil {
c.Input = *dec.Input
}
if dec.Output != nil {
c.Output = *dec.Output
}
if dec.Error != nil {
c.Error = *dec.Error
}
if dec.RevertReason != nil {
c.RevertReason = *dec.RevertReason
}
if dec.Logs != nil {
c.Logs = dec.Logs
}
if dec.Value != nil {
c.Value = (*big.Int)(dec.Value)
}
if dec.AccessedSlots != nil {
c.AccessedSlots = *dec.AccessedSlots
}
if dec.ExtCodeAccessInfo != nil {
c.ExtCodeAccessInfo = dec.ExtCodeAccessInfo
}
if dec.UsedOpcodes != nil {
c.UsedOpcodes = make(map[vm.OpCode]uint64, len(dec.UsedOpcodes))
for k, v := range dec.UsedOpcodes {
c.UsedOpcodes[vm.OpCode(k)] = v
}
}
if dec.ContractSize != nil {
c.ContractSize = dec.ContractSize
}
if dec.OutOfGas != nil {
c.OutOfGas = *dec.OutOfGas
}
if dec.KeccakPreimages != nil {
c.KeccakPreimages = make([][]byte, len(dec.KeccakPreimages))
for k, v := range dec.KeccakPreimages {
c.KeccakPreimages[k] = v
}
}
if dec.Calls != nil {
c.Calls = dec.Calls
}
return nil
}

View file

@ -1,151 +0,0 @@
// Copyright 2019 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 build
import (
"bufio"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
)
// ChecksumDB keeps file checksums.
type ChecksumDB struct {
allChecksums []string
}
// MustLoadChecksums loads a file containing checksums.
func MustLoadChecksums(file string) *ChecksumDB {
content, err := os.ReadFile(file)
if err != nil {
log.Fatal("can't load checksum file: " + err.Error())
}
return &ChecksumDB{strings.Split(strings.ReplaceAll(string(content), "\r\n", "\n"), "\n")}
}
// Verify checks whether the given file is valid according to the checksum database.
func (db *ChecksumDB) Verify(path string) error {
fd, err := os.Open(path)
if err != nil {
return err
}
defer fd.Close()
h := sha256.New()
if _, err := io.Copy(h, bufio.NewReader(fd)); err != nil {
return err
}
fileHash := hex.EncodeToString(h.Sum(nil))
if !db.findHash(filepath.Base(path), fileHash) {
return fmt.Errorf("invalid file hash: %s %s", fileHash, filepath.Base(path))
}
return nil
}
func (db *ChecksumDB) findHash(basename, hash string) bool {
want := hash + " " + basename
for _, line := range db.allChecksums {
if strings.TrimSpace(line) == want {
return true
}
}
return false
}
// DownloadFile downloads a file and verifies its checksum.
func (db *ChecksumDB) DownloadFile(url, dstPath string) error {
if err := db.Verify(dstPath); err == nil {
fmt.Printf("%s is up-to-date\n", dstPath)
return nil
}
fmt.Printf("%s is stale\n", dstPath)
fmt.Printf("downloading from %s\n", url)
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("download error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("download error: status %d", resp.StatusCode)
}
if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
return err
}
fd, err := os.OpenFile(dstPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
return err
}
dst := newDownloadWriter(fd, resp.ContentLength)
_, err = io.Copy(dst, resp.Body)
dst.Close()
if err != nil {
return err
}
return db.Verify(dstPath)
}
type downloadWriter struct {
file *os.File
dstBuf *bufio.Writer
size int64
written int64
lastpct int64
}
func newDownloadWriter(dst *os.File, size int64) *downloadWriter {
return &downloadWriter{
file: dst,
dstBuf: bufio.NewWriter(dst),
size: size,
}
}
func (w *downloadWriter) Write(buf []byte) (int, error) {
n, err := w.dstBuf.Write(buf)
// Report progress.
w.written += int64(n)
pct := w.written * 10 / w.size * 10
if pct != w.lastpct {
if w.lastpct != 0 {
fmt.Print("...")
}
fmt.Print(pct, "%")
w.lastpct = pct
}
return n, err
}
func (w *downloadWriter) Close() error {
if w.lastpct > 0 {
fmt.Println() // Finish the progress line.
}
flushErr := w.dstBuf.Flush()
closeErr := w.file.Close()
if flushErr != nil {
return flushErr
}
return closeErr
}

View file

@ -24,6 +24,8 @@ import (
"path/filepath"
"runtime"
"strings"
"github.com/ethereum/go-ethereum/internal/download"
)
type GoToolchain struct {
@ -84,8 +86,8 @@ func (g *GoToolchain) goTool(command string, args ...string) *exec.Cmd {
// DownloadGo downloads the Go binary distribution and unpacks it into a temporary
// directory. It returns the GOROOT of the unpacked toolchain.
func DownloadGo(csdb *ChecksumDB) string {
version, err := Version(csdb, "golang")
func DownloadGo(csdb *download.ChecksumDB) string {
version, err := csdb.FindVersion("golang")
if err != nil {
log.Fatal(err)
}
@ -130,51 +132,3 @@ func DownloadGo(csdb *ChecksumDB) string {
}
return goroot
}
// Version returns the versions defined in the checksumdb.
func Version(csdb *ChecksumDB, version string) (string, error) {
for _, l := range csdb.allChecksums {
if !strings.HasPrefix(l, "# version:") {
continue
}
v := strings.Split(l, ":")[1]
parts := strings.Split(v, " ")
if len(parts) != 2 {
log.Print("Erroneous version-string", "v", l)
continue
}
if parts[0] == version {
return parts[1], nil
}
}
return "", fmt.Errorf("no version found for '%v'", version)
}
// DownloadAndVerifyChecksums downloads all files and checks that they match
// the checksum given in checksums.txt.
// This task can be used to sanity-check new checksums.
func DownloadAndVerifyChecksums(csdb *ChecksumDB) {
var (
base = ""
ucache = os.TempDir()
)
for _, l := range csdb.allChecksums {
if strings.HasPrefix(l, "# https://") {
base = l[2:]
continue
}
if strings.HasPrefix(l, "#") {
continue
}
hashFile := strings.Split(l, " ")
if len(hashFile) != 2 {
continue
}
file := hashFile[1]
url := base + file
dst := filepath.Join(ucache, file)
if err := csdb.DownloadFile(url, dst); err != nil {
log.Print(err)
}
}
}

View file

@ -0,0 +1,298 @@
// Copyright 2019 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 download implements checksum-verified file downloads.
package download
import (
"bufio"
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"iter"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
)
// ChecksumDB keeps file checksums and tool versions.
type ChecksumDB struct {
hashes []hashEntry
versions []versionEntry
}
type versionEntry struct {
name string
version string
}
type hashEntry struct {
hash string
file string
url *url.URL
}
// MustLoadChecksums loads a file containing checksums.
func MustLoadChecksums(file string) *ChecksumDB {
content, err := os.ReadFile(file)
if err != nil {
panic("can't load checksum file: " + err.Error())
}
db, err := ParseChecksums(content)
if err != nil {
panic(fmt.Sprintf("invalid checksums in %s: %v", file, err))
}
return db
}
// ParseChecksums parses a checksum database.
func ParseChecksums(input []byte) (*ChecksumDB, error) {
var (
csdb = new(ChecksumDB)
rd = bytes.NewBuffer(input)
lastURL *url.URL
)
for lineNum := 1; ; lineNum++ {
line, err := rd.ReadString('\n')
if err == io.EOF {
break
}
line = strings.TrimSpace(line)
switch {
case line == "":
// Blank lines are allowed, and they reset the current urlEntry.
lastURL = nil
case strings.HasPrefix(line, "#"):
// It's a comment. Some comments have special meaning.
content := strings.TrimLeft(line, "# ")
switch {
case strings.HasPrefix(content, "version:"):
// Version comments define the version of a tool.
v := strings.Split(content, ":")[1]
parts := strings.Split(v, " ")
if len(parts) != 2 {
return nil, fmt.Errorf("line %d: invalid version string: %q", lineNum, v)
}
csdb.versions = append(csdb.versions, versionEntry{parts[0], parts[1]})
case strings.HasPrefix(content, "https://") || strings.HasPrefix(content, "http://"):
// URL comments define the URL where the following files are found. Here
// we keep track of the last found urlEntry and attach it to each file later.
u, err := url.Parse(content)
if err != nil {
return nil, fmt.Errorf("line %d: invalid URL: %v", lineNum, err)
}
lastURL = u
}
default:
// It's a file hash entry.
fields := strings.Fields(line)
if len(fields) != 2 {
return nil, fmt.Errorf("line %d: invalid number of space-separated fields (%d)", lineNum, len(fields))
}
csdb.hashes = append(csdb.hashes, hashEntry{fields[0], fields[1], lastURL})
}
}
return csdb, nil
}
// Files returns an iterator over all file names.
func (db *ChecksumDB) Files() iter.Seq[string] {
return func(yield func(string) bool) {
for _, e := range db.hashes {
if !yield(e.file) {
return
}
}
}
}
// DownloadAndVerifyAll downloads all files and checks that they match the checksum given in
// the database. This task can be used to sanity-check new checksums.
func (db *ChecksumDB) DownloadAndVerifyAll() {
var tmp = os.TempDir()
for _, e := range db.hashes {
if e.url == nil {
fmt.Printf("Skipping verification of %s: no URL defined in checksum database", e.file)
continue
}
url := e.url.JoinPath(e.file).String()
dst := filepath.Join(tmp, e.file)
if err := db.DownloadFile(url, dst); err != nil {
fmt.Println("error:", err)
}
}
}
// verifyHash checks that the file at 'path' has the expected hash.
func verifyHash(path, expectedHash string) error {
fd, err := os.Open(path)
if err != nil {
return err
}
defer fd.Close()
h := sha256.New()
if _, err := io.Copy(h, bufio.NewReader(fd)); err != nil {
return err
}
fileHash := hex.EncodeToString(h.Sum(nil))
if fileHash != expectedHash {
return fmt.Errorf("invalid file hash: %s %s", fileHash, filepath.Base(path))
}
return nil
}
// DownloadFileFromKnownURL downloads a file from the URL defined in the checksum database.
func (db *ChecksumDB) DownloadFileFromKnownURL(dstPath string) error {
base := filepath.Base(dstPath)
url, err := db.FindURL(base)
if err != nil {
return err
}
return db.DownloadFile(url, dstPath)
}
// DownloadFile downloads a file and verifies its checksum.
func (db *ChecksumDB) DownloadFile(url, dstPath string) error {
basename := filepath.Base(dstPath)
hash := db.findHash(basename)
if hash == "" {
return fmt.Errorf("no known hash for file %q", basename)
}
// Shortcut if already downloaded.
if verifyHash(dstPath, hash) == nil {
fmt.Printf("%s is up-to-date\n", dstPath)
return nil
}
fmt.Printf("%s is stale\n", dstPath)
fmt.Printf("downloading from %s\n", url)
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("download error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("download error: status %d", resp.StatusCode)
}
if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
return err
}
// Download to a temporary file.
tmpfile := dstPath + ".tmp"
fd, err := os.OpenFile(tmpfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
return err
}
dst := newDownloadWriter(fd, resp.ContentLength)
_, err = io.Copy(dst, resp.Body)
dst.Close()
if err != nil {
os.Remove(tmpfile)
return err
}
if err := verifyHash(tmpfile, hash); err != nil {
os.Remove(tmpfile)
return err
}
// It's valid, rename to dstPath to complete the download.
return os.Rename(tmpfile, dstPath)
}
// findHash returns the known hash of a file.
func (db *ChecksumDB) findHash(basename string) string {
for _, e := range db.hashes {
if e.file == basename {
return e.hash
}
}
return ""
}
// FindVersion returns the current known version of a tool, if it is defined in the file.
func (db *ChecksumDB) FindVersion(tool string) (string, error) {
for _, e := range db.versions {
if e.name == tool {
return e.version, nil
}
}
return "", fmt.Errorf("tool version %q not defined in checksum database", tool)
}
// FindURL gets the URL for a file.
func (db *ChecksumDB) FindURL(basename string) (string, error) {
for _, e := range db.hashes {
if e.file == basename {
if e.url == nil {
return "", fmt.Errorf("file %q has no URL defined", e.file)
}
return e.url.JoinPath(e.file).String(), nil
}
}
return "", fmt.Errorf("file %q does not exist in checksum database", basename)
}
type downloadWriter struct {
file *os.File
dstBuf *bufio.Writer
size int64
written int64
lastpct int64
}
func newDownloadWriter(dst *os.File, size int64) *downloadWriter {
return &downloadWriter{
file: dst,
dstBuf: bufio.NewWriter(dst),
size: size,
}
}
func (w *downloadWriter) Write(buf []byte) (int, error) {
n, err := w.dstBuf.Write(buf)
// Report progress.
w.written += int64(n)
pct := w.written * 10 / w.size * 10
if pct != w.lastpct {
if w.lastpct != 0 {
fmt.Print("...")
}
fmt.Print(pct, "%")
w.lastpct = pct
}
return n, err
}
func (w *downloadWriter) Close() error {
if w.lastpct > 0 {
fmt.Println() // Finish the progress line.
}
flushErr := w.dstBuf.Flush()
closeErr := w.file.Close()
if flushErr != nil {
return flushErr
}
return closeErr
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,183 @@
ab7f6d4f4eba0267406f783b75accc7a93dece520242d04fed27b0af51d79242 sepolia-00000-643a00f7.era1
9cae627459d13ed3d05f6812eefa93242555fbefd27aa0927b40b62d44feb2e9 sepolia-00001-f3ea493e.era1
a6f691585bc74fae6c445a8985f0df342e983ba1ef569018befb4b21b30891e2 sepolia-00002-dcb01f4d.era1
1add5a98a9e6c15a667d6a7bbdaea115893019f3033664c54c2dcab70829268d sepolia-00003-18b32584.era1
5e5ce2ca04b0f1aef6f026214cb64d16f76606fb5b5baf8a462f4a851dda0513 sepolia-00004-e93b3f11.era1
db6f7687e9826a4e4dbc1361d666a8f2aa735eae309f63d9cadf6b27a899277b sepolia-00005-90918472.era1
f61118a4e1cb718bdb71fde1daf84e59c3524b1e241ea1a5e2112d8c53acc625 sepolia-00006-a4a583b1.era1
fd0c6fc443a4c30617ce88a48e7438fecaf4cf4772034511fa51433c7aae181a sepolia-00007-ef1e0a86.era1
95bc416c6393e0a579f9e4af42421cf861e43354d49273d369bcf9b38e070246 sepolia-00008-dfe1e6dd.era1
8262e7182f9a8abb9f5fc05d745a3cd2061eacda5c87cc2533af2407bd20672c sepolia-00009-dba60c04.era1
d7c123c8e06713df23db0046df7c45d873e153ff076357b6d0c5bc9b0081a246 sepolia-00010-c99c706d.era1
7fd359358ebf710c5c61a6a65551a4cb91c2160dbd7b59ed355a4d7e10f29328 sepolia-00011-04513853.era1
9d6ea741d1b91e1c2c37ac1659ef3516d4968b6e7c164d347e534c4bebe644fd sepolia-00012-6e07302d.era1
ce3e580fd3e5cb9e0ba9ebffae948406fdbe59f96fa3a05a30a381bc7d1b583e sepolia-00013-25709be2.era1
15df46dc8c2cfd402f302f2925d1c50ac12538d394e8687920f53346429d6743 sepolia-00014-7c90e5a5.era1
1985aaeb96c81279b978beab1145b3baf460b12bff86b8ca05c5067e1b0ca25b sepolia-00015-fadc08ab.era1
2a53126cd443e9310fa15180c5bbbdb76f257d7fee67159500d8efa0ed13bef4 sepolia-00016-d44b2499.era1
e783cff42ba7a53461c7fcdaf59a5387e7091a1230aa6761af05e7daf59b4805 sepolia-00017-02451eb3.era1
92848afbeea81397fc9790e7fba7c627c01c17cbc5b8961a48ca60329bbfa3f3 sepolia-00018-cdb53b11.era1
d15ac188a4dfac75818dfeb04e72c2f269d9f985100a457ebdc17661e36f0d83 sepolia-00019-fa770019.era1
e8ef40780f55a0b640d1b68e1a89d1d438e8105bb1388bffc8a743b88e357671 sepolia-00020-15fc80e7.era1
8041d790a0c50044e331a385eb98b17f5050ab25637b6a4f2c3e8e99f3f81e6e sepolia-00021-b8814b14.era1
f17adda073b66cc97cb58acc7b776296e135943718dab9fc7cfecbac748419b7 sepolia-00022-87944c43.era1
159a5ec4fa1e79be6d13fe4753eaa8994f547e31783069082a39d51e3e00b178 sepolia-00023-516a30f5.era1
5b75090c9f26899aa6fe504963b7f1a43ab13a8fd7547da42f9ca9cc980511de sepolia-00024-b922dde6.era1
d5c39668b309b43eec720b2185ac23ef819cc7fa92e35f2e6c9a4ce9ee1c249d sepolia-00025-a081e175.era1
188b5119e110feec067dc06aefde7decba2a87bde642cc1735391a1782a99b03 sepolia-00026-d3931a12.era1
3114068f4eb9cf8277d4417da371e820f0bf1a654600f8dea149f9854523d1f2 sepolia-00027-43f5e0ac.era1
b18597daf66f27976638a84a4cac5622f7d53fca8264dac3665f84f28435cc26 sepolia-00028-dae08170.era1
d0672ee04c5a84e881c4acc3757e16f963c57b5b69da81a674498c1b06159f3e sepolia-00029-17818143.era1
122dff18aaac0bae0a006da088794f638ee95ddaa1e0a347ca9a8eca816471c1 sepolia-00030-cc7fc4eb.era1
42c2206fda093c7aa6b31fad1addf2dd14e862b4686b0e17050bd284dc479cfd sepolia-00031-3171eede.era1
42b023d07a02ad739b61ec8297c43501ddac200d1866fa3203df466d2442c4b0 sepolia-00032-9e2ff5ca.era1
2461fb005a9a62cf6ab52a0b3505abde9828c1f36def4fa12a547ebb5e8c89c0 sepolia-00033-bc921023.era1
2ca97708fba029693d7754a85424d29f57ee296a48fcbde02597603e7e90525d sepolia-00034-b2496634.era1
d2944e1cbd7a3276ca5ef9d4b4473557a65e58bd512029b2fb00d41f618ac551 sepolia-00035-f3add4f1.era1
c7f63d7230dd7ae9c57c33014d56bd4c921f5db95f3872bf6745d03bc64b5cc0 sepolia-00036-0db12924.era1
dc027011c543418fcfdd7897cea3356de16122f676da2459d78da5369cdc2b8f sepolia-00037-ae8bdd13.era1
9be2262ef4e1e1b93cee3316ad473895bd7a7f3bff15ac8c18c1a157292ab7f6 sepolia-00038-81f80b03.era1
56c03c86028c95d431030fa2728493ca8001cc86eec3f5d7a558224e550b42ef sepolia-00039-03e5d6f1.era1
d64e65ecfcf480c2edbdebac350ac899b25874a522c226695a46fab17de5b15c sepolia-00040-296fe287.era1
18226ba389f150581b36a234d877fad9c08c0fa691a366ba11f808ec9110cf67 sepolia-00041-5cd87470.era1
0d2960f23933deadcdfe545fc8aba626e79c0c010fad0ac01b577ca5b99840e0 sepolia-00042-de86936c.era1
b14f66c2fc74d0f772eb75c548a86da4bf160cf1067091c65c05a90812e7af1f sepolia-00043-ede4e682.era1
15d704883fadfcbe4ce3520bc9ed5fcff98a8a419b6fa9f832394008ae1941aa sepolia-00044-4d49a81a.era1
5343d8015eb0a6b8728b571fd0e7a2bb92728ec593469b0a9d8d2082a5afc36d sepolia-00045-7071c19b.era1
63efe606b8c687aeeff4d2ff5596a72da862d024f9dbb1ff5bf67ca4f2921f77 sepolia-00046-4f4fe79f.era1
9eb6dd3772ebfe63c37a22423310a5cb36c0d09defd66d7aa02a5b4c1283ca80 sepolia-00047-76b58fb3.era1
82afedad4e56f86d836446f0c71616f8d88fdec11b93a6360dacc111cdb12db2 sepolia-00048-0fa9d93c.era1
8a86584323e83a11499265f33ddabe0de1517b51c8e73985fa6eed15cfd63d0b sepolia-00049-d193ff47.era1
b66621a148fb7acce05efe6aab89be5a965463a4c4c95a3350e64f3e8a684417 sepolia-00050-736b969f.era1
49d47547284339de6f92e6495347dce0c45c5294f07c1b96fcfa510c43cfc206 sepolia-00051-466eb482.era1
b075750d6efdde0cd755c90cd893b2917c56ab8674022fb36bb7a1305dc9bfed sepolia-00052-9752212c.era1
36eb486d7dab70e759c72198b9f2505ac3fcb7f6a0ae2cca82fd9ade3d65e86d sepolia-00053-b2897233.era1
9f024ea4e135fe6939f2fedb6a6821d61202139fa35378616fcf0274bdeac172 sepolia-00054-aedad6a8.era1
2bc46d10add9c9903d554523f6ed0c8f5a632521c659d9f3f4af4c444221d08c sepolia-00055-19af5091.era1
21900526ff5f0f31257bdabaae3fffa700eccd3cf3a6ba346b4bdf4347c97324 sepolia-00056-9ac921a1.era1
c9a47d7db6af1b984e6ad8de2ea0eb0e73e442281d4fa497ba64c02caaf0b7d0 sepolia-00057-240f011a.era1
10d38d3b6b66367a1c05922040073d6a62a2cbb843ea821dbd02f4d9f037c740 sepolia-00058-06d606d6.era1
d391f44dfa9bab3ced35676cd964daeffe0f4e4028c3bd44fe41010b761c5229 sepolia-00059-8ea69a55.era1
39a1bdf4d7c1dd8365f635c8da568b34d1c2e59aa5d7fe8745d6b7db2cf2c6ed sepolia-00060-d22079b0.era1
cf41ae65b6391d8c44bb0128e66591a96d42fea0a7d35c30c8eca5bbe0790578 sepolia-00061-834d00e3.era1
9bcff9fa47c2addc140857b23d6453583ca526c6cdf11144583db24acf33b1b2 sepolia-00062-d1230cc2.era1
984c5662f6f36ece4e0ca9efc47dcb3e50e0aa581a874a680736f2f558cdd889 sepolia-00063-e1e9fa40.era1
7582532b67144ea9499c28d935e7ac4bbf8f465c382a8a97ce04e56539b8c9af sepolia-00064-7f827781.era1
945e3da724660df934484d68868d54fbfaa5feaacdd635f6185b56e88858ad8d sepolia-00065-9d8993d6.era1
77fdff04d6d6233c4bab9811ad559a328fdb0447070b96e2f38ea7450639a00e sepolia-00066-8d516260.era1
3486301cf48b1f727b46c0223c4a018e03d0857efedc2f616ea850b539139f93 sepolia-00067-7466141b.era1
2954560d2b1c6ff48242312b7f5fe902c53dc93a3d1c7d9abf41633926ef72ac sepolia-00068-1bd11e8a.era1
8f27de663991d7e4e8a7005dee7b94f991cfefad743507362eca5ad83f53e449 sepolia-00069-536ecd2c.era1
2d0fa324482aab3db61718abfb2a5c084a55db5f39c4bce125255eec9a56a01a sepolia-00070-b27f7c5c.era1
472b1c9ec83d9b6564a45d4363c856604d21b53d8c89a7b5810f2596c14f710a sepolia-00071-d2ef5349.era1
3a0e6051e4c5f1a263b51f7a827d2276c7b25cb9d426f24f39fca33a7e76d623 sepolia-00072-b23578a9.era1
734b46f0bb7150e1d705bff53a0cdedd3ed57ca1461f06deb517c8b74c4b25c9 sepolia-00073-8bacb416.era1
28091f1a78dc398e2d47212087f652c8d35f07cda5457ee691b4191c3a6698c8 sepolia-00074-0e81003c.era1
b743e3e1721e593562d9a144f49a927bbe2db70be9e4884521cae9500b0004cb sepolia-00075-09177034.era1
6850f49599b95dcf1db54a3c5d126654ca21de4df27b7a24bc35c4d10fe6cdb7 sepolia-00076-0f2898a3.era1
da57f20ff5a3944bd1fef22e7c914f2eda3e79dd7346b0e7c0a01e69cf538d7e sepolia-00077-61de3538.era1
fc20551b69bacce502fa42ba5b0a223c9fb558d5f495948e38c060328f1ceb7e sepolia-00078-be407b86.era1
ab72961502e1d8055886d744c077dbf60db8acba64c7879109b3ed573ce4cd02 sepolia-00079-d5c957be.era1
216b65dba90adf1288d40fc689f6087ff2435bd379df482b130cd464cbd1d490 sepolia-00080-ae6ce310.era1
e824e04c1cf15f142bdbb40161b64953767abd3ec7c40a726ce69d4ce646df08 sepolia-00081-daac26d6.era1
d70d059609dda26e25af96d32b646c364e2ae44fe7b1cf9e2e8589d731e9f381 sepolia-00082-ecb8f0c2.era1
a7dc282ca7fd7aa8d42693363aec655d6cfea330220233c7aad3f7955284d650 sepolia-00083-5b017cd4.era1
2f1006528caa6d47729c4ef0a699c441cb5d2c201afb6563ab51184429294020 sepolia-00084-8c1f92cd.era1
374494754cfd728b1b4e4a00db810012346ba8c4dc9cf2ad1d80187999ab868b sepolia-00085-9180667c.era1
7ff9b360a0656ddbace4032d249f5cf06068ee3f8be526d2a4ae0ed6eaef046f sepolia-00086-1aca7add.era1
de592f4bde8de973d8c82a7d89dbe21cf75bb0b28204567ea4f5012b8be1f28c sepolia-00087-0e100944.era1
fb6eb1e1e1ce97e03b39fe2d53e3595d759701a65cc861d0568c04ee7e2ec03c sepolia-00088-fcdd7aa5.era1
4d178ce50f5436f5ba2347d87dc79e9a2e18c17d3056eb77c7c8dbb9b03bb986 sepolia-00089-b23b2368.era1
21e919f0d32137e7924d54ddcf1e697bb5b57bbb7cc12a169475df993c4c1d9f sepolia-00090-b78edeb7.era1
cb7ae025ca17c5f52ef7910147a47a0d3680c26f9d2d109d6af330f93f95b735 sepolia-00091-4c81cdca.era1
1e0249554f150b63cb7dd57c53c624c197b415401c0a1f3986c2f06e8aaa795c sepolia-00092-ddbf3a4c.era1
8d950d8c13a4e002514a47e6d6ebcad4bf7e5808398641a5c04863d9c90f0f87 sepolia-00093-a52a45ec.era1
b8720324f04dca4e0046b12ec3b8a2b862158f3156c4d1fc2947340b1fc1a9cb sepolia-00094-839ea4e5.era1
8c4134bbb21d36fbd7ba608eb1e485e76cb0fd56c4aa8032be1b6848757856d9 sepolia-00095-bccc0958.era1
e3ad213e760ea2320ff52caf5de32ced37d6b7ad0f99384785ca42a7e58d1cd9 sepolia-00096-429b2f39.era1
9c324e109f77ebc1750dd2a0941904ff820047def91f8b832e61c01b31297f38 sepolia-00097-6f74184d.era1
26efc4469736b283fd80d9105fc85a70dbec209fbb0e1e40a905a9406e1bb3cc sepolia-00098-40286059.era1
e14134b3aad0533366f9aeb19cf2561cf1e81b989a0b83f12b05493948dd71ea sepolia-00099-dd1ef4f4.era1
7283a437244545e784d51186c0d7d4809808dd472850ca3cd2d7a40f4883b50d sepolia-00100-bd3d6bf4.era1
b67270e94377703aac798d8323c0a91b451d36806775013a2b8b12db04797851 sepolia-00101-41676e0d.era1
dd6ea805242a67c6255867f64fc5fbc941a98318b5ea1d5e2d42d42c6910493c sepolia-00102-4f3c1fe1.era1
d6f6da94b16a201b304936cb5f798b6fedba76ead056cd2c4dc860c21d82b8dd sepolia-00103-e13a95f0.era1
e2309561f7a86eddfe182dc86fb38e5454c5a272cc77b3d0d7c6ce987ddc874c sepolia-00104-4e49b068.era1
deff905084758be35bf0b8cb640b1b283232abbc6153388a4275c767ea0bdf0e sepolia-00105-4ab7d9ea.era1
964f9679a39fe06192a7025f14404cb1f6542071665f1c2f739f62fdb55e9274 sepolia-00106-5ec678af.era1
8fd75370992f97e77a2cd3663bc1fb9b2bcec9b64a0439de7846d7c6321e6d93 sepolia-00107-44721c55.era1
7e8219aa13d6a7b32c3a69888a2c5f446703eada3fdcde841ba26edfe3cb4453 sepolia-00108-3b4ad768.era1
24e8d2f8d01abf63d97365bafa914dd5203f4e807bc6655dc0673b9f87215ebd sepolia-00109-16e54758.era1
edadb0ebec6c4b7b54237e5802cc59fd962e23cf690ff10ab52c10258d7b7827 sepolia-00110-b5da103b.era1
4faebfbdc009188cc8cf0ffe4aeca9c93ac5b3abe763cfb165d8bed37b12b51c sepolia-00111-15c53aba.era1
40210479bd14ca3f8db76b288c2ed4e8f3d5b5ce8116ba8d8fe3133b0cc765ad sepolia-00112-8e2144e3.era1
92ed406d564ed1504bc7ff91298a419fe72a710585256b1830040ba3d1adbde8 sepolia-00113-1e1aac0a.era1
7e771bba1aa9c87be155a9a7d62d3479240f21934a3d763cafa5f3cd4ce49f8a sepolia-00114-0d7a4b23.era1
51d71ed3674f5d70f719b3251e29a0825f2573b1bfd1a92940b013806aaf4fd3 sepolia-00115-8e983a59.era1
6abea02d82f7a30b67d6de4516bbc711855eea3d891ea42fa0124c101f981f86 sepolia-00116-1708ba6e.era1
88e7222e35b1275483b6eec11bf8d4417602fdb16b611d6c154abd8d0ec0b49a sepolia-00117-86962046.era1
6ddeae324cc17337fa23c71b242ab1675b72f43fb393af7e971edec20451f864 sepolia-00118-934e9f5f.era1
914c35142f7244d07b670b1f990c8bb5997119db451e8dcd8a67c8d71fcc6af2 sepolia-00119-4d88db3e.era1
eedec437c182f330f2f5f3c530cce3bc59f0c99c9e6cd36288a588adba7679bb sepolia-00120-1a3274d5.era1
4914f6a8cf63cb806c5736431f6402d9a99c2de953da05a17069fa15cf8c695b sepolia-00121-62b79757.era1
b9a0138c77dcbb2b4784813204ec8bc30407cd8daf3501707719985acff4a1cb sepolia-00122-41d14652.era1
57453e0eaa2bf46a13c94d9e96ee98943a46012c2bdd9da5d7c77064e803fbfd sepolia-00123-67f86708.era1
1e927fdb13cebda45a963c8cec921b16c795fb3020cad9054fa1fb7f4cb528cd sepolia-00124-488cb226.era1
8015846c8c3663005a8a6d59347fb672a602e6bbff028989355a01f5f6d2b183 sepolia-00125-8a21f7da.era1
8807f6abe63b76f31d99cf14404fee8bb5eba3a814e2d08b9d85540ef9742550 sepolia-00126-6569478a.era1
28dc46c8120426b395cf9b5ff781962a3212766b24511b2dc3d2172568b10021 sepolia-00127-1312ab23.era1
ed66119ec9dae6c61952cbf40b9dc5770cd80e659a83dd18e1a5f2ffa947a04b sepolia-00128-1c95bdc6.era1
a4a68dd54b77f21939ddb1ec9ef9ae0a3551d6d53b8f9578b01ad8e32e5c8d8e sepolia-00129-f32fdefa.era1
e54d6d4b95a985f37391750b3bf3bc0f9f4e2f790805797ee198d77287433ca2 sepolia-00130-8f31c590.era1
577fe68c5c33e47af8823ac573709ed9d63df0d7f9aec02a7907e3e6e5922d67 sepolia-00131-5aaad354.era1
c3beada9620c55b6d0dbdab8f66b735da0b3d62d5c5eb2c3b5369619549fb3e0 sepolia-00132-2147d970.era1
12843e128e76d5636f857c8c6801e433757fc4f8aba159bc2d4e6c41f2c94abd sepolia-00133-690cc16f.era1
e5e4904ec83ebcebccc1aae1de89622a50bf6f735d985234da88fa1ced1787c0 sepolia-00134-fe276e6b.era1
9c618458f6d76a09f16c86baf526dc7f2310ce92d862f73392144987c04b39ed sepolia-00135-ce40efd0.era1
1d032d1279c3841c65385f85463226df04a98a3ccc5d7bf461f042492230615c sepolia-00136-adc6e1bb.era1
a3aa401ca2bc8cb8b104667c6f68e28d00e37813a1a993d8abd1ec5c3555f71d sepolia-00137-805a333a.era1
105c1e50e73715bf58d42f6bbe49bff36dc0051bfb477efc77ebf14b5ba98974 sepolia-00138-65ff170f.era1
ad53fc0e4d2718ce105fa3a7ce5574dbb945f49b2fa7470a406179c45738fc46 sepolia-00139-1c86d86a.era1
c27ae427ca0052686a3ac8a3e568d1dae30449f9edba465f218c147074d61ccc sepolia-00140-e7f36280.era1
143d09ffb26cd2a1bb334958d92b77a92a8f9ac99c01059d22bee4e4d8bc3212 sepolia-00141-5d3d0d5a.era1
378a77f0b0c4072297de4c382cece46be521a6c6935db8eb499dd99857d566aa sepolia-00142-e45aa418.era1
35855d5bd75dff01248dd5b2080babf6127d87c157a987124f7a258cfb9a5ecb sepolia-00143-70379f59.era1
58f07f98d8e9486acd01ec212518b1c0d7764e68263f9b1e9c51cf5e597a0b6b sepolia-00144-c345e75c.era1
17caa603e2d1f9c01c9117754f0bb4c8ce42c58da806f758f99468d9f8c1c47b sepolia-00145-9f4bb4c5.era1
d05a92b8fe1306397b98a6202b18f66c9bc259bd41c25b3d8211c09813e3a9ac sepolia-00146-3336d26b.era1
b7120a656d06d4010a06a2abadd809ee7f26707cf313e9de5cb1bd172745ad23 sepolia-00147-6b007873.era1
aa45cc5747e9c21ccfc44c87f98717702551a0511f423b2624a34af58ba798fc sepolia-00148-35f8a5b4.era1
952843d531ea4d9ee9c456e50e77c50f75a559bff13326dd593271f90cd55129 sepolia-00149-01b6e9ca.era1
2d93d1d84e0f4013d6daab61d66907488223a2f1b50e4a22d3ba1da24a9c6a84 sepolia-00150-0c372e80.era1
6b516e3f31720c54453c5ce9bbac68300615f455a7cf4b1dc6592a5d5432ee89 sepolia-00151-c15a5955.era1
82023cab017453e8fcf49ace9befbaef4af167e0e6c6976456bf251f803b5d51 sepolia-00152-ef7ac893.era1
6ace677a17380127349ba0710fd02ff38ba185526cc2d9bbb6f05375d6f163f6 sepolia-00153-0e4073c2.era1
66db5e0786cb9963ed66a0e31773b30d3405621a7636c62ffbb0b84ff41705bd sepolia-00154-298c3549.era1
cf797283087a079773770c1bebfecb113a4155e0d77d5713c702478e2c24f64a sepolia-00155-a99309bb.era1
b143cc994d0b544cd08b2a1f66308718ea49f3f68adef8e62300e91f8048130a sepolia-00156-8c33148f.era1
cbc20f9f767324438a237c3114a1c4e65dda74d4a655d55a7cc3e63e62f373f8 sepolia-00157-ab02fc10.era1
c8368d1752e66bcd7cabc653477f9aec0071027f91112a8cf71a945db23404e7 sepolia-00158-17992856.era1
03a1a66eab3a7cbc63b29cfbe2e70285940e64db4af6d23ab70592c820f8d3ea sepolia-00159-b3448cf6.era1
bf9dcd8400e56c58cd3280f0a3d7aab23a7eaf594e67db5387f40101ad8e8c92 sepolia-00160-acad7054.era1
03a3b975b2a78a4b28afd9ee089440465ceefd32bd1d82b74ae69caa80b6fb6c sepolia-00161-36e611bd.era1
ddf201b59ebc77eccdec97b0a5a7d7a89dda02928ff7c5cca8868b5aba84f533 sepolia-00162-3557cc58.era1
041be469daf6f7abaa0cfe66c4d68b7d9d223383553c16f078edb3a1fa3d0d92 sepolia-00163-638b9afe.era1
68d151048412318cbd0af98efa27d7ee16e2fc58830d93b4d76b66cb632ebfc3 sepolia-00164-fbc67b64.era1
3025771380736a5de790ead991c0512a4c73106fd1d2f169863d395964238ac1 sepolia-00165-442b83a1.era1
6d6b1273a6fc353c6999b4c9be301660a404b78cced911e230991305da990e3c sepolia-00166-018915fc.era1
01052c4f779e4ec276498d4192b8ad11ad6d2d3992224bb62ea882a80deda935 sepolia-00167-0df2b4c5.era1
2f8f3034eb7e41c5add0e639e21b52c0794f36bb5918274849402c5fe0d247e6 sepolia-00168-7cab66f5.era1
504b0203d995e5ef6aee679b048c276f45b05dc68a6fd319a0f4f0133b230a77 sepolia-00169-75850131.era1
b277df79fe5a83189f9619b275a9aaab63db8506539c56997be61fd5a3552bb9 sepolia-00170-c2044b78.era1
1af0a1778fc5d2a53aaca06dbda6671b91ae3f56da73a6c6708bc729e5fb9efd sepolia-00171-af44ff2e.era1
a977654c0ed243ccce7b5514914ec94d70a3a742ff014ac52b0654e21940f2da sepolia-00172-bd76a8d9.era1
146672b2e63c320c9474ecf41d7f32f31f5298512cc8f911074c88789849d935 sepolia-00173-b6924da5.era1
82af1c5bc51f9e9ae1be5d0e0827ea755d104ed373b374c08ef401115491671f sepolia-00174-2a61fac5.era1
627ee6cf9282191ff32d291fc0437b812bad01f158b5ca42ae717b7bbb125f8c sepolia-00175-1cc76405.era1
6ee38f117646a47f6ab2f666fa6460bda770c194ae4a29744534e9bfd9e7ba3b sepolia-00176-20daa2c6.era1
28e7e49e71dd8581cc484d8be89fb083a79577b06f26c1e3bfd4a57bdf24c5e7 sepolia-00177-379df7e4.era1
055c365cfeda3495a5e2b334803dfffe7af2a584745acabc42b5ad146b250534 sepolia-00178-88c9d2aa.era1
bca9a920232983636b7cf20fc8319f891df94ba0860f51b5a052290b58d9e3ae sepolia-00179-8329b80c.era1
e894a6f3ce0ede2825aaafa64122a5f458cd3b2ecaeae20a44a59fd668971bb0 sepolia-00180-22de0418.era1
83bfe0d7c49c3a108a4ed044119a3549f2f4b0f8e74c971150b6977b7d46a250 sepolia-00181-b3fbe6f2.era1
3b806177536ac3615f376440c0589696ea746eea0215bb3b175df8f4f1e73894 sepolia-00182-a4f0a8a1.era1

115
internal/era/eradl/eradl.go Normal file
View file

@ -0,0 +1,115 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package eradl implements downloading of era1 files.
package eradl
import (
_ "embed"
"fmt"
"net/url"
"path/filepath"
"regexp"
"strconv"
"github.com/ethereum/go-ethereum/internal/download"
"github.com/ethereum/go-ethereum/internal/era"
)
//go:embed checksums_mainnet.txt
var mainnetDB []byte
//go:embed checksums_sepolia.txt
var sepoliaDB []byte
type Loader struct {
csdb *download.ChecksumDB
network string
baseURL *url.URL
}
// New creates an era1 loader for the given server URL and network name.
func New(baseURL string, network string) (*Loader, error) {
var checksums []byte
switch network {
case "mainnet":
checksums = mainnetDB
case "sepolia":
checksums = sepoliaDB
default:
return nil, fmt.Errorf("missing era1 checksum definitions for network %q", network)
}
csdb, err := download.ParseChecksums(checksums)
if err != nil {
return nil, fmt.Errorf("invalid checksums: %v", err)
}
base, err := url.Parse(baseURL)
if err != nil {
return nil, fmt.Errorf("invalid base URL %q: %v", baseURL, err)
}
if base.Scheme != "http" && base.Scheme != "https" {
return nil, fmt.Errorf("invalid base URL scheme, expected http(s): %q", baseURL)
}
l := &Loader{
network: network,
csdb: csdb,
baseURL: base,
}
return l, nil
}
// DownloadAll downloads all known era1 files to the given directory.
func (l *Loader) DownloadAll(destDir string) error {
for file := range l.csdb.Files() {
if err := l.download(file, destDir); err != nil {
return err
}
}
return nil
}
// DownloadBlockRange fetches the era1 files for the given block range.
func (l *Loader) DownloadBlockRange(start, end uint64, destDir string) error {
startEpoch := start / uint64(era.MaxEra1Size)
endEpoch := end / uint64(era.MaxEra1Size)
return l.DownloadEpochRange(startEpoch, endEpoch, destDir)
}
// DownloadEpochRange fetches the era1 files in the given epoch range.
func (l *Loader) DownloadEpochRange(start, end uint64, destDir string) error {
pat := regexp.MustCompile(regexp.QuoteMeta(l.network) + "-([0-9]+)-[0-9a-f]+\\.era1")
for file := range l.csdb.Files() {
m := pat.FindStringSubmatch(file)
if len(m) == 2 {
fileEpoch, _ := strconv.Atoi(m[1])
if uint64(fileEpoch) >= start && uint64(fileEpoch) <= end {
if err := l.download(file, destDir); err != nil {
return err
}
}
}
}
return nil
}
func (l *Loader) download(file, destDir string) error {
url := l.baseURL.JoinPath(file).String()
dest := filepath.Join(destDir, file)
return l.csdb.DownloadFile(url, dest)
}

View file

@ -5435,36 +5435,6 @@ var methods = function () {
outputFormatter: utils.toDecimal
});
var compileSolidity = new Method({
name: 'compile.solidity',
call: 'eth_compileSolidity',
params: 1
});
var compileLLL = new Method({
name: 'compile.lll',
call: 'eth_compileLLL',
params: 1
});
var compileSerpent = new Method({
name: 'compile.serpent',
call: 'eth_compileSerpent',
params: 1
});
var submitWork = new Method({
name: 'submitWork',
call: 'eth_submitWork',
params: 3
});
var getWork = new Method({
name: 'getWork',
call: 'eth_getWork',
params: 0
});
return [
getBalance,
getStorageAt,
@ -5483,12 +5453,7 @@ var methods = function () {
sendRawTransaction,
signTransaction,
sendTransaction,
sign,
compileSolidity,
compileLLL,
compileSerpent,
submitWork,
getWork
sign
];
};

View file

@ -1,5 +1,5 @@
// This file originates from Docker/Moby,
// https://github.com/moby/moby/blob/master/pkg/reexec/reexec.go
// https://github.com/moby/moby/blob/master/pkg/reexec/reexec_deprecated.go
// Licensed under Apache License 2.0: https://github.com/moby/moby/blob/master/LICENSE
// Copyright 2013-2018 Docker, Inc.
//

View file

@ -30,6 +30,35 @@ type Iterator interface {
Close() // ends the iterator
}
// SourceIterator represents a sequence of nodes like [Iterator]
// Each node also has a named 'source'.
type SourceIterator interface {
Iterator
NodeSource() string // source of current node
}
// WithSource attaches a 'source name' to an iterator.
func WithSourceName(name string, it Iterator) SourceIterator {
return sourceIter{it, name}
}
func ensureSourceIter(it Iterator) SourceIterator {
if si, ok := it.(SourceIterator); ok {
return si
}
return WithSourceName("", it)
}
type sourceIter struct {
Iterator
name string
}
// NodeSource implements IteratorSource.
func (it sourceIter) NodeSource() string {
return it.name
}
// ReadNodes reads at most n nodes from the given iterator. The return value contains no
// duplicates and no nil values. To prevent looping indefinitely for small repeating node
// sequences, this function calls Next at most n times.
@ -106,16 +135,16 @@ func (it *sliceIter) Close() {
// Filter wraps an iterator such that Next only returns nodes for which
// the 'check' function returns true.
func Filter(it Iterator, check func(*Node) bool) Iterator {
return &filterIter{it, check}
return &filterIter{ensureSourceIter(it), check}
}
type filterIter struct {
Iterator
SourceIterator
check func(*Node) bool
}
func (f *filterIter) Next() bool {
for f.Iterator.Next() {
for f.SourceIterator.Next() {
if f.check(f.Node()) {
return true
}
@ -135,9 +164,9 @@ func (f *filterIter) Next() bool {
// It's safe to call AddSource and Close concurrently with Next.
type FairMix struct {
wg sync.WaitGroup
fromAny chan *Node
fromAny chan mixItem
timeout time.Duration
cur *Node
cur mixItem
mu sync.Mutex
closed chan struct{}
@ -146,11 +175,16 @@ type FairMix struct {
}
type mixSource struct {
it Iterator
next chan *Node
it SourceIterator
next chan mixItem
timeout time.Duration
}
type mixItem struct {
n *Node
source string
}
// NewFairMix creates a mixer.
//
// The timeout specifies how long the mixer will wait for the next fairly-chosen source
@ -159,7 +193,7 @@ type mixSource struct {
// timeout makes the mixer completely fair.
func NewFairMix(timeout time.Duration) *FairMix {
m := &FairMix{
fromAny: make(chan *Node),
fromAny: make(chan mixItem),
closed: make(chan struct{}),
timeout: timeout,
}
@ -175,7 +209,11 @@ func (m *FairMix) AddSource(it Iterator) {
return
}
m.wg.Add(1)
source := &mixSource{it, make(chan *Node), m.timeout}
source := &mixSource{
it: ensureSourceIter(it),
next: make(chan mixItem),
timeout: m.timeout,
}
m.sources = append(m.sources, source)
go m.runSource(m.closed, source)
}
@ -201,7 +239,7 @@ func (m *FairMix) Close() {
// Next returns a node from a random source.
func (m *FairMix) Next() bool {
m.cur = nil
m.cur = mixItem{}
for {
source := m.pickSource()
@ -217,12 +255,12 @@ func (m *FairMix) Next() bool {
}
select {
case n, ok := <-source.next:
case item, ok := <-source.next:
if ok {
// Here, the timeout is reset to the configured value
// because the source delivered a node.
source.timeout = m.timeout
m.cur = n
m.cur = item
return true
}
// This source has ended.
@ -239,15 +277,20 @@ func (m *FairMix) Next() bool {
// Node returns the current node.
func (m *FairMix) Node() *Node {
return m.cur
return m.cur.n
}
// NodeSource returns the current node's source name.
func (m *FairMix) NodeSource() string {
return m.cur.source
}
// nextFromAny is used when there are no sources or when the 'fair' choice
// doesn't turn up a node quickly enough.
func (m *FairMix) nextFromAny() bool {
n, ok := <-m.fromAny
item, ok := <-m.fromAny
if ok {
m.cur = n
m.cur = item
}
return ok
}
@ -284,10 +327,10 @@ func (m *FairMix) runSource(closed chan struct{}, s *mixSource) {
defer m.wg.Done()
defer close(s.next)
for s.it.Next() {
n := s.it.Node()
item := mixItem{s.it.Node(), s.it.NodeSource()}
select {
case s.next <- n:
case m.fromAny <- n:
case s.next <- item:
case m.fromAny <- item:
case <-closed:
return
}

View file

@ -19,6 +19,7 @@ package enode
import (
"encoding/binary"
"runtime"
"slices"
"sync/atomic"
"testing"
"time"
@ -183,6 +184,53 @@ func TestFairMixRemoveSource(t *testing.T) {
}
}
// This checks that FairMix correctly returns the name of the source that produced the node.
func TestFairMixSourceName(t *testing.T) {
nodes := make([]*Node, 6)
for i := range nodes {
nodes[i] = testNode(uint64(i), uint64(i))
}
mix := NewFairMix(-1)
mix.AddSource(WithSourceName("s1", IterNodes(nodes[0:2])))
mix.AddSource(WithSourceName("s2", IterNodes(nodes[2:4])))
mix.AddSource(WithSourceName("s3", IterNodes(nodes[4:6])))
var names []string
for range nodes {
mix.Next()
names = append(names, mix.NodeSource())
}
want := []string{"s2", "s3", "s1", "s2", "s3", "s1"}
if !slices.Equal(names, want) {
t.Fatalf("wrong names: %v", names)
}
}
// This checks that FairMix returns the name of the source that produced the node,
// even when FairMix instances are nested.
func TestFairMixNestedSourceName(t *testing.T) {
nodes := make([]*Node, 6)
for i := range nodes {
nodes[i] = testNode(uint64(i), uint64(i))
}
mix := NewFairMix(-1)
mix.AddSource(WithSourceName("s1", IterNodes(nodes[0:2])))
submix := NewFairMix(-1)
submix.AddSource(WithSourceName("s2", IterNodes(nodes[2:4])))
submix.AddSource(WithSourceName("s3", IterNodes(nodes[4:6])))
mix.AddSource(submix)
var names []string
for range nodes {
mix.Next()
names = append(names, mix.NodeSource())
}
want := []string{"s3", "s1", "s2", "s1", "s3", "s2"}
if !slices.Equal(names, want) {
t.Fatalf("wrong names: %v", names)
}
}
type blockingIter chan struct{}
func (it blockingIter) Next() bool {

View file

@ -187,8 +187,10 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t
}
// Cross-check the snapshot-to-hash against the trie hash
if snapshotter {
if err := chain.Snapshots().Verify(chain.CurrentBlock().Root); err != nil {
return err
if chain.Snapshots() != nil {
if err := chain.Snapshots().Verify(chain.CurrentBlock().Root); err != nil {
return err
}
}
}
return t.validateImportedHeaders(chain, validBlocks)

View file

@ -25,7 +25,7 @@ import (
"github.com/ethereum/go-ethereum/triedb/database"
)
// testReader implements database.Reader interface, providing function to
// testReader implements database.NodeReader interface, providing function to
// access trie nodes.
type testReader struct {
db ethdb.Database
@ -33,7 +33,7 @@ type testReader struct {
nodes []*trienode.MergedNodeSet // sorted from new to old
}
// Node implements database.Reader interface, retrieving trie node with
// Node implements database.NodeReader interface, retrieving trie node with
// all available cached layers.
func (r *testReader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) {
// Check the node presence with the cached layer, from latest to oldest.
@ -54,7 +54,7 @@ func (r *testReader) Node(owner common.Hash, path []byte, hash common.Hash) ([]b
return rawdb.ReadTrieNode(r.db, owner, path, hash, r.scheme), nil
}
// testDb implements database.Database interface, using for testing purpose.
// testDb implements database.NodeDatabase interface, using for testing purpose.
type testDb struct {
disk ethdb.Database
root common.Hash

View file

@ -19,6 +19,7 @@ package trie
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb/database"
@ -63,8 +64,7 @@ type StateTrie struct {
trie Trie
db database.NodeDatabase
preimages preimageStore
hashKeyBuf [common.HashLength]byte
secKeyCache map[string][]byte
secKeyCache map[common.Hash][]byte
secKeyCacheOwner *StateTrie // Pointer to self, replace the key cache on mismatch
}
@ -97,7 +97,7 @@ func NewStateTrie(id *ID, db database.NodeDatabase) (*StateTrie, error) {
// This function will omit any encountered error but just
// print out an error message.
func (t *StateTrie) MustGet(key []byte) []byte {
return t.trie.MustGet(t.hashKey(key))
return t.trie.MustGet(crypto.Keccak256(key))
}
// GetStorage attempts to retrieve a storage slot with provided account address
@ -105,7 +105,7 @@ func (t *StateTrie) MustGet(key []byte) []byte {
// If the specified storage slot is not in the trie, nil will be returned.
// If a trie node is not found in the database, a MissingNodeError is returned.
func (t *StateTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) {
enc, err := t.trie.Get(t.hashKey(key))
enc, err := t.trie.Get(crypto.Keccak256(key))
if err != nil || len(enc) == 0 {
return nil, err
}
@ -117,7 +117,7 @@ func (t *StateTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) {
// If the specified account is not in the trie, nil will be returned.
// If a trie node is not found in the database, a MissingNodeError is returned.
func (t *StateTrie) GetAccount(address common.Address) (*types.StateAccount, error) {
res, err := t.trie.Get(t.hashKey(address.Bytes()))
res, err := t.trie.Get(crypto.Keccak256(address.Bytes()))
if res == nil || err != nil {
return nil, err
}
@ -157,9 +157,9 @@ func (t *StateTrie) GetNode(path []byte) ([]byte, int, error) {
// This function will omit any encountered error but just print out an
// error message.
func (t *StateTrie) MustUpdate(key, value []byte) {
hk := t.hashKey(key)
hk := crypto.Keccak256(key)
t.trie.MustUpdate(hk, value)
t.getSecKeyCache()[string(hk)] = common.CopyBytes(key)
t.getSecKeyCache()[common.Hash(hk)] = common.CopyBytes(key)
}
// UpdateStorage associates key with value in the trie. Subsequent calls to
@ -171,19 +171,19 @@ func (t *StateTrie) MustUpdate(key, value []byte) {
//
// If a node is not found in the database, a MissingNodeError is returned.
func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error {
hk := t.hashKey(key)
hk := crypto.Keccak256(key)
v, _ := rlp.EncodeToBytes(value)
err := t.trie.Update(hk, v)
if err != nil {
return err
}
t.getSecKeyCache()[string(hk)] = common.CopyBytes(key)
t.getSecKeyCache()[common.Hash(hk)] = common.CopyBytes(key)
return nil
}
// UpdateAccount will abstract the write of an account to the secure trie.
func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount, _ int) error {
hk := t.hashKey(address.Bytes())
hk := crypto.Keccak256(address.Bytes())
data, err := rlp.EncodeToBytes(acc)
if err != nil {
return err
@ -191,7 +191,7 @@ func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccoun
if err := t.trie.Update(hk, data); err != nil {
return err
}
t.getSecKeyCache()[string(hk)] = address.Bytes()
t.getSecKeyCache()[common.Hash(hk)] = address.Bytes()
return nil
}
@ -202,8 +202,8 @@ func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte
// MustDelete removes any existing value for key from the trie. This function
// will omit any encountered error but just print out an error message.
func (t *StateTrie) MustDelete(key []byte) {
hk := t.hashKey(key)
delete(t.getSecKeyCache(), string(hk))
hk := crypto.Keccak256(key)
delete(t.getSecKeyCache(), common.Hash(hk))
t.trie.MustDelete(hk)
}
@ -211,22 +211,22 @@ func (t *StateTrie) MustDelete(key []byte) {
// If the specified trie node is not in the trie, nothing will be changed.
// If a node is not found in the database, a MissingNodeError is returned.
func (t *StateTrie) DeleteStorage(_ common.Address, key []byte) error {
hk := t.hashKey(key)
delete(t.getSecKeyCache(), string(hk))
hk := crypto.Keccak256(key)
delete(t.getSecKeyCache(), common.Hash(hk))
return t.trie.Delete(hk)
}
// DeleteAccount abstracts an account deletion from the trie.
func (t *StateTrie) DeleteAccount(address common.Address) error {
hk := t.hashKey(address.Bytes())
delete(t.getSecKeyCache(), string(hk))
hk := crypto.Keccak256(address.Bytes())
delete(t.getSecKeyCache(), common.Hash(hk))
return t.trie.Delete(hk)
}
// GetKey returns the sha3 preimage of a hashed key that was
// previously used to store a value.
func (t *StateTrie) GetKey(shaKey []byte) []byte {
if key, ok := t.getSecKeyCache()[string(shaKey)]; ok {
if key, ok := t.getSecKeyCache()[common.BytesToHash(shaKey)]; ok {
return key
}
if t.preimages == nil {
@ -251,13 +251,9 @@ func (t *StateTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
// Write all the pre-images to the actual disk database
if len(t.getSecKeyCache()) > 0 {
if t.preimages != nil {
preimages := make(map[common.Hash][]byte, len(t.secKeyCache))
for hk, key := range t.secKeyCache {
preimages[common.BytesToHash([]byte(hk))] = key
}
t.preimages.InsertPreimage(preimages)
t.preimages.InsertPreimage(t.secKeyCache)
}
t.secKeyCache = make(map[string][]byte)
t.secKeyCache = make(map[common.Hash][]byte)
}
// Commit the trie and return its modified nodeset.
return t.trie.Commit(collectLeaf)
@ -291,25 +287,13 @@ func (t *StateTrie) MustNodeIterator(start []byte) NodeIterator {
return t.trie.MustNodeIterator(start)
}
// hashKey returns the hash of key as an ephemeral buffer.
// The caller must not hold onto the return value because it will become
// invalid on the next call to hashKey or secKey.
func (t *StateTrie) hashKey(key []byte) []byte {
h := newHasher(false)
h.sha.Reset()
h.sha.Write(key)
h.sha.Read(t.hashKeyBuf[:])
returnHasherToPool(h)
return t.hashKeyBuf[:]
}
// getSecKeyCache returns the current secure key cache, creating a new one if
// ownership changed (i.e. the current secure trie is a copy of another owning
// the actual cache).
func (t *StateTrie) getSecKeyCache() map[string][]byte {
func (t *StateTrie) getSecKeyCache() map[common.Hash][]byte {
if t != t.secKeyCacheOwner {
t.secKeyCacheOwner = t
t.secKeyCache = make(map[string][]byte)
t.secKeyCache = make(map[common.Hash][]byte)
}
return t.secKeyCache
}

View file

@ -312,6 +312,26 @@ func (db *Database) Journal(root common.Hash) error {
return pdb.Journal(root)
}
// AccountIterator creates a new account iterator for the specified root hash and
// seeks to a starting account hash.
func (db *Database) AccountIterator(root common.Hash, seek common.Hash) (pathdb.AccountIterator, error) {
pdb, ok := db.backend.(*pathdb.Database)
if !ok {
return nil, errors.New("not supported")
}
return pdb.AccountIterator(root, seek)
}
// StorageIterator creates a new storage iterator for the specified root hash and
// account. The iterator will be move to the specific start position.
func (db *Database) StorageIterator(root common.Hash, account common.Hash, seek common.Hash) (pathdb.StorageIterator, error) {
pdb, ok := db.backend.(*pathdb.Database)
if !ok {
return nil, errors.New("not supported")
}
return pdb.StorageIterator(root, account, seek)
}
// IsVerkle returns the indicator if the database is holding a verkle tree.
func (db *Database) IsVerkle() bool {
return db.config.IsVerkle

View file

@ -124,7 +124,7 @@ func (b *buffer) size() uint64 {
// flush persists the in-memory dirty trie node into the disk if the configured
// memory threshold is reached. Note, all data must be written atomically.
func (b *buffer) flush(db ethdb.KeyValueStore, freezer ethdb.AncientWriter, nodesCache *fastcache.Cache, id uint64) error {
func (b *buffer) flush(root common.Hash, db ethdb.KeyValueStore, freezer ethdb.AncientWriter, progress []byte, nodesCache, statesCache *fastcache.Cache, id uint64) error {
// Ensure the target state id is aligned with the internal counter.
head := rawdb.ReadPersistentStateID(db)
if head+b.layers != id {
@ -133,7 +133,7 @@ func (b *buffer) flush(db ethdb.KeyValueStore, freezer ethdb.AncientWriter, node
// Terminate the state snapshot generation if it's active
var (
start = time.Now()
batch = db.NewBatchWithSize(b.nodes.dbsize() * 11 / 10) // extra 10% for potential pebble internal stuff
batch = db.NewBatchWithSize((b.nodes.dbsize() + b.states.dbsize()) * 11 / 10) // extra 10% for potential pebble internal stuff
)
// Explicitly sync the state freezer to ensure all written data is persisted to disk
// before updating the key-value store.
@ -146,7 +146,9 @@ func (b *buffer) flush(db ethdb.KeyValueStore, freezer ethdb.AncientWriter, node
}
}
nodes := b.nodes.write(batch, nodesCache)
accounts, slots := b.states.write(batch, progress, statesCache)
rawdb.WritePersistentStateID(batch, id)
rawdb.WriteSnapshotRoot(batch, root)
// Flush all mutations in a single batch
size := batch.ValueSize()
@ -155,8 +157,10 @@ func (b *buffer) flush(db ethdb.KeyValueStore, freezer ethdb.AncientWriter, node
}
commitBytesMeter.Mark(int64(size))
commitNodesMeter.Mark(int64(nodes))
commitAccountsMeter.Mark(int64(accounts))
commitStoragesMeter.Mark(int64(slots))
commitTimeTimer.UpdateSince(start)
b.reset()
log.Debug("Persisted buffer content", "nodes", nodes, "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start)))
log.Debug("Persisted buffer content", "nodes", nodes, "accounts", accounts, "slots", slots, "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start)))
return nil
}

246
triedb/pathdb/context.go Normal file
View file

@ -0,0 +1,246 @@
// Copyright 2024 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 pathdb
import (
"bytes"
"encoding/binary"
"errors"
"math"
"time"
"github.com/ethereum/go-ethereum/common"
"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/log"
)
const (
snapAccount = "account" // Identifier of account snapshot generation
snapStorage = "storage" // Identifier of storage snapshot generation
)
// generatorStats is a collection of statistics gathered by the snapshot generator
// for logging purposes. This data structure is used throughout the entire
// lifecycle of the snapshot generation process and is shared across multiple
// generation cycles.
type generatorStats struct {
origin uint64 // Origin prefix where generation started
start time.Time // Timestamp when generation started
accounts uint64 // Number of accounts indexed(generated or recovered)
slots uint64 // Number of storage slots indexed(generated or recovered)
dangling uint64 // Number of dangling storage slots
storage common.StorageSize // Total account and storage slot size(generation or recovery)
}
// log creates a contextual log with the given message and the context pulled
// from the internally maintained statistics.
func (gs *generatorStats) log(msg string, root common.Hash, marker []byte) {
var ctx []interface{}
if root != (common.Hash{}) {
ctx = append(ctx, []interface{}{"root", root}...)
}
// Figure out whether we're after or within an account
switch len(marker) {
case common.HashLength:
ctx = append(ctx, []interface{}{"at", common.BytesToHash(marker)}...)
case 2 * common.HashLength:
ctx = append(ctx, []interface{}{
"in", common.BytesToHash(marker[:common.HashLength]),
"at", common.BytesToHash(marker[common.HashLength:]),
}...)
}
// Add the usual measurements
ctx = append(ctx, []interface{}{
"accounts", gs.accounts,
"slots", gs.slots,
"storage", gs.storage,
"dangling", gs.dangling,
"elapsed", common.PrettyDuration(time.Since(gs.start)),
}...)
// Calculate the estimated indexing time based on current stats
if len(marker) > 0 {
if done := binary.BigEndian.Uint64(marker[:8]) - gs.origin; done > 0 {
left := math.MaxUint64 - binary.BigEndian.Uint64(marker[:8])
speed := done/uint64(time.Since(gs.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
ctx = append(ctx, []interface{}{
"eta", common.PrettyDuration(time.Duration(left/speed) * time.Millisecond),
}...)
}
}
log.Info(msg, ctx...)
}
// generatorContext holds several global fields that are used throughout the
// current generation cycle. It must be recreated if the generation cycle is
// restarted.
type generatorContext struct {
root common.Hash // State root of the generation target
account *holdableIterator // Iterator of account snapshot data
storage *holdableIterator // Iterator of storage snapshot data
db ethdb.KeyValueStore // Key-value store containing the snapshot data
batch ethdb.Batch // Database batch for writing data atomically
logged time.Time // The timestamp when last generation progress was displayed
}
// newGeneratorContext initializes the context for generation.
func newGeneratorContext(root common.Hash, marker []byte, db ethdb.KeyValueStore) *generatorContext {
ctx := &generatorContext{
root: root,
db: db,
batch: db.NewBatch(),
logged: time.Now(),
}
accMarker, storageMarker := splitMarker(marker)
ctx.openIterator(snapAccount, accMarker)
ctx.openIterator(snapStorage, storageMarker)
return ctx
}
// openIterator constructs global account and storage snapshot iterators
// at the interrupted position. These iterators should be reopened from time
// to time to avoid blocking leveldb compaction for a long time.
func (ctx *generatorContext) openIterator(kind string, start []byte) {
if kind == snapAccount {
iter := ctx.db.NewIterator(rawdb.SnapshotAccountPrefix, start)
ctx.account = newHoldableIterator(rawdb.NewKeyLengthIterator(iter, 1+common.HashLength))
return
}
iter := ctx.db.NewIterator(rawdb.SnapshotStoragePrefix, start)
ctx.storage = newHoldableIterator(rawdb.NewKeyLengthIterator(iter, 1+2*common.HashLength))
}
// reopenIterator releases the specified snapshot iterator and re-open it
// in the next position. It's aimed for not blocking leveldb compaction.
func (ctx *generatorContext) reopenIterator(kind string) {
// Shift iterator one more step, so that we can reopen
// the iterator at the right position.
var iter = ctx.account
if kind == snapStorage {
iter = ctx.storage
}
hasNext := iter.Next()
if !hasNext {
// Iterator exhausted, release forever and create an already exhausted virtual iterator
iter.Release()
if kind == snapAccount {
ctx.account = newHoldableIterator(memorydb.New().NewIterator(nil, nil))
return
}
ctx.storage = newHoldableIterator(memorydb.New().NewIterator(nil, nil))
return
}
next := iter.Key()
iter.Release()
ctx.openIterator(kind, next[1:])
}
// close releases all the held resources.
func (ctx *generatorContext) close() {
ctx.account.Release()
ctx.storage.Release()
}
// iterator returns the corresponding iterator specified by the kind.
func (ctx *generatorContext) iterator(kind string) *holdableIterator {
if kind == snapAccount {
return ctx.account
}
return ctx.storage
}
// removeStorageBefore deletes all storage entries which are located before
// the specified account. When the iterator touches the storage entry which
// is located in or outside the given account, it stops and holds the current
// iterated element locally.
func (ctx *generatorContext) removeStorageBefore(account common.Hash) uint64 {
var (
count uint64
start = time.Now()
iter = ctx.storage
)
for iter.Next() {
key := iter.Key()
if bytes.Compare(key[1:1+common.HashLength], account.Bytes()) >= 0 {
iter.Hold()
break
}
count++
ctx.batch.Delete(key)
if ctx.batch.ValueSize() > ethdb.IdealBatchSize {
ctx.batch.Write()
ctx.batch.Reset()
}
}
storageCleanCounter.Inc(time.Since(start).Nanoseconds())
return count
}
// removeStorageAt deletes all storage entries which are located in the specified
// account. When the iterator touches the storage entry which is outside the given
// account, it stops and holds the current iterated element locally. An error will
// be returned if the initial position of iterator is not in the given account.
func (ctx *generatorContext) removeStorageAt(account common.Hash) error {
var (
count int64
start = time.Now()
iter = ctx.storage
)
for iter.Next() {
key := iter.Key()
cmp := bytes.Compare(key[1:1+common.HashLength], account.Bytes())
if cmp < 0 {
return errors.New("invalid iterator position")
}
if cmp > 0 {
iter.Hold()
break
}
count++
ctx.batch.Delete(key)
if ctx.batch.ValueSize() > ethdb.IdealBatchSize {
ctx.batch.Write()
ctx.batch.Reset()
}
}
wipedStorageMeter.Mark(count)
storageCleanCounter.Inc(time.Since(start).Nanoseconds())
return nil
}
// removeRemainingStorage deletes all storage entries which are located after
// the current iterator position.
func (ctx *generatorContext) removeRemainingStorage() uint64 {
var (
count uint64
start = time.Now()
iter = ctx.storage
)
for iter.Next() {
count++
ctx.batch.Delete(iter.Key())
if ctx.batch.ValueSize() > ethdb.IdealBatchSize {
ctx.batch.Write()
ctx.batch.Reset()
}
}
danglingStorageMeter.Mark(int64(count))
storageCleanCounter.Inc(time.Since(start).Nanoseconds())
return count
}

View file

@ -17,6 +17,7 @@
package pathdb
import (
"encoding/binary"
"errors"
"fmt"
"io"
@ -35,8 +36,11 @@ import (
)
const (
// defaultCleanSize is the default memory allowance of clean cache.
defaultCleanSize = 16 * 1024 * 1024
// defaultTrieCleanSize is the default memory allowance of clean trie cache.
defaultTrieCleanSize = 16 * 1024 * 1024
// defaultStateCleanSize is the default memory allowance of clean state cache.
defaultStateCleanSize = 16 * 1024 * 1024
// maxBufferSize is the maximum memory allowance of node buffer.
// Too large buffer will cause the system to pause for a long
@ -111,9 +115,11 @@ type layer interface {
// Config contains the settings for database.
type Config struct {
StateHistory uint64 // Number of recent blocks to maintain state history for
CleanCacheSize int // Maximum memory allowance (in bytes) for caching clean nodes
TrieCleanSize int // Maximum memory allowance (in bytes) for caching clean trie nodes
StateCleanSize int // Maximum memory allowance (in bytes) for caching clean state data
WriteBufferSize int // Maximum memory allowance (in bytes) for write buffer
ReadOnly bool // Flag whether the database is opened in read only mode.
ReadOnly bool // Flag whether the database is opened in read only mode
SnapshotNoBuild bool // Flag Whether the background generation is allowed
}
// sanitize checks the provided user configurations and changes anything that's
@ -133,7 +139,11 @@ func (c *Config) fields() []interface{} {
if c.ReadOnly {
list = append(list, "readonly", true)
}
list = append(list, "cache", common.StorageSize(c.CleanCacheSize))
if c.SnapshotNoBuild {
list = append(list, "snapshot", false)
}
list = append(list, "triecache", common.StorageSize(c.TrieCleanSize))
list = append(list, "statecache", common.StorageSize(c.StateCleanSize))
list = append(list, "buffer", common.StorageSize(c.WriteBufferSize))
list = append(list, "history", c.StateHistory)
return list
@ -142,7 +152,8 @@ func (c *Config) fields() []interface{} {
// Defaults contains default settings for Ethereum mainnet.
var Defaults = &Config{
StateHistory: params.FullImmutabilityThreshold,
CleanCacheSize: defaultCleanSize,
TrieCleanSize: defaultTrieCleanSize,
StateCleanSize: defaultStateCleanSize,
WriteBufferSize: defaultBufferSize,
}
@ -240,6 +251,13 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database {
log.Crit("Failed to disable database", "err", err) // impossible to happen
}
}
// Resolving the state snapshot generation progress from the database is
// mandatory. This ensures that uncovered flat states are not accessed,
// even if background generation is not allowed. If permitted, the generation
// might be scheduled.
if err := db.setStateGenerator(); err != nil {
log.Crit("Failed to setup the generator", "err", err)
}
fields := config.fields()
if db.isVerkle {
fields = append(fields, "verkle", true)
@ -297,6 +315,60 @@ func (db *Database) repairHistory() error {
return nil
}
// setStateGenerator loads the state generation progress marker and potentially
// resume the state generation if it's permitted.
func (db *Database) setStateGenerator() error {
// Load the state snapshot generation progress marker to prevent access
// to uncovered states.
generator, root, err := loadGenerator(db.diskdb, db.hasher)
if err != nil {
return err
}
if generator == nil {
// Initialize an empty generator to rebuild the state snapshot from scratch
generator = &journalGenerator{
Marker: []byte{},
}
}
// Short circuit if the whole state snapshot has already been fully generated.
// The generator will be left as nil in disk layer for representing the whole
// state snapshot is available for accessing.
if generator.Done {
return nil
}
var origin uint64
if len(generator.Marker) >= 8 {
origin = binary.BigEndian.Uint64(generator.Marker)
}
stats := &generatorStats{
origin: origin,
start: time.Now(),
accounts: generator.Accounts,
slots: generator.Slots,
storage: common.StorageSize(generator.Storage),
}
dl := db.tree.bottom()
// Disable the background snapshot building in these circumstances:
// - the database is opened in read only mode
// - the snapshot build is explicitly disabled
// - the database is opened in verkle tree mode
noBuild := db.readOnly || db.config.SnapshotNoBuild || db.isVerkle
// Construct the generator and link it to the disk layer, ensuring that the
// generation progress is resolved to prevent accessing uncovered states
// regardless of whether background state snapshot generation is allowed.
dl.setGenerator(newGenerator(db.diskdb, noBuild, generator.Marker, stats))
// Short circuit if the background generation is not permitted
if noBuild || db.waitSync {
return nil
}
stats.log("Starting snapshot generation", root, generator.Marker)
dl.generator.run(root)
return nil
}
// Update adds a new layer into the tree, if that can be linked to an existing
// old parent. It is disallowed to insert a disk layer (the origin of all). Apart
// from that this function will flatten the extra diff layers at bottom into disk
@ -359,8 +431,13 @@ func (db *Database) Disable() error {
}
db.waitSync = true
// Mark the disk layer as stale to prevent access to persistent state.
db.tree.bottom().markStale()
// Terminate the state generator if it's active and mark the disk layer
// as stale to prevent access to persistent state.
disk := db.tree.bottom()
if disk.generator != nil {
disk.generator.stop()
}
disk.markStale()
// Write the initial sync flag to persist it across restarts.
rawdb.WriteSnapSyncStatusFlag(db.diskdb, rawdb.StateSyncRunning)
@ -390,6 +467,7 @@ func (db *Database) Enable(root common.Hash) error {
// reset the persistent state id back to zero.
batch := db.diskdb.NewBatch()
rawdb.DeleteTrieJournal(batch)
rawdb.DeleteSnapshotRoot(batch)
rawdb.WritePersistentStateID(batch, 0)
if err := batch.Write(); err != nil {
return err
@ -403,13 +481,13 @@ func (db *Database) Enable(root common.Hash) error {
return err
}
}
// Re-construct a new disk layer backed by persistent state
// with **empty clean cache and node buffer**.
db.tree.reset(newDiskLayer(root, 0, db, nil, newBuffer(db.config.WriteBufferSize, nil, nil, 0)))
// Re-enable the database as the final step.
db.waitSync = false
rawdb.WriteSnapSyncStatusFlag(db.diskdb, rawdb.StateSyncFinished)
// Re-construct a new disk layer backed by persistent state
// and schedule the state snapshot generation if it's permitted.
db.tree.reset(generateSnapshot(db, root, db.isVerkle || db.config.SnapshotNoBuild))
log.Info("Rebuilt trie database", "root", root)
return nil
}
@ -514,8 +592,12 @@ func (db *Database) Close() error {
// following mutations.
db.readOnly = true
// Release the memory held by clean cache.
db.tree.bottom().resetCache()
// Terminate the background generation if it's active
disk := db.tree.bottom()
if disk.generator != nil {
disk.generator.stop()
}
disk.resetCache() // release the memory held by clean cache
// Close the attached state history freezer.
if db.freezer == nil {
@ -580,14 +662,42 @@ func (db *Database) HistoryRange() (uint64, uint64, error) {
return historyRange(db.freezer)
}
// waitGeneration waits until the background generation is finished. It assumes
// that the generation is permitted; otherwise, it will block indefinitely.
func (db *Database) waitGeneration() {
gen := db.tree.bottom().generator
if gen == nil || gen.completed() {
return
}
<-gen.done
}
// AccountIterator creates a new account iterator for the specified root hash and
// seeks to a starting account hash.
func (db *Database) AccountIterator(root common.Hash, seek common.Hash) (AccountIterator, error) {
db.lock.RLock()
wait := db.waitSync
db.lock.RUnlock()
if wait {
return nil, errDatabaseWaitSync
}
if gen := db.tree.bottom().generator; gen != nil && !gen.completed() {
return nil, errNotConstructed
}
return newFastAccountIterator(db, root, seek)
}
// StorageIterator creates a new storage iterator for the specified root hash and
// account. The iterator will be moved to the specific start position.
func (db *Database) StorageIterator(root common.Hash, account common.Hash, seek common.Hash) (StorageIterator, error) {
db.lock.RLock()
wait := db.waitSync
db.lock.RUnlock()
if wait {
return nil, errDatabaseWaitSync
}
if gen := db.tree.bottom().generator; gen != nil && !gen.completed() {
return nil, errNotConstructed
}
return newFastStorageIterator(db, root, account, seek)
}

View file

@ -126,7 +126,8 @@ func newTester(t *testing.T, historyLimit uint64, isVerkle bool, layers int) *te
disk, _ = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
db = New(disk, &Config{
StateHistory: historyLimit,
CleanCacheSize: 256 * 1024,
TrieCleanSize: 256 * 1024,
StateCleanSize: 256 * 1024,
WriteBufferSize: 256 * 1024,
}, isVerkle)

View file

@ -17,9 +17,10 @@
package pathdb
import (
"errors"
"bytes"
"fmt"
"sync"
"time"
"github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common"
@ -30,28 +31,42 @@ import (
// diskLayer is a low level persistent layer built on top of a key-value store.
type diskLayer struct {
root common.Hash // Immutable, root hash to which this layer was made for
id uint64 // Immutable, corresponding state id
db *Database // Path-based trie database
root common.Hash // Immutable, root hash to which this layer was made for
id uint64 // Immutable, corresponding state id
db *Database // Path-based trie database
// These two caches must be maintained separately, because the key
// for the root node of the storage trie (accountHash) is identical
// to the key for the account data.
nodes *fastcache.Cache // GC friendly memory cache of clean nodes
buffer *buffer // Dirty buffer to aggregate writes of nodes and states
stale bool // Signals that the layer became stale (state progressed)
lock sync.RWMutex // Lock used to protect stale flag
states *fastcache.Cache // GC friendly memory cache of clean states
buffer *buffer // Dirty buffer to aggregate writes of nodes and states
stale bool // Signals that the layer became stale (state progressed)
lock sync.RWMutex // Lock used to protect stale flag and genMarker
// The generator is set if the state snapshot was not fully completed,
// regardless of whether the background generation is running or not.
generator *generator
}
// newDiskLayer creates a new disk layer based on the passing arguments.
func newDiskLayer(root common.Hash, id uint64, db *Database, nodes *fastcache.Cache, buffer *buffer) *diskLayer {
// Initialize a clean cache if the memory allowance is not zero
// or reuse the provided cache if it is not nil (inherited from
func newDiskLayer(root common.Hash, id uint64, db *Database, nodes *fastcache.Cache, states *fastcache.Cache, buffer *buffer) *diskLayer {
// Initialize the clean caches if the memory allowance is not zero
// or reuse the provided caches if they are not nil (inherited from
// the original disk layer).
if nodes == nil && db.config.CleanCacheSize != 0 {
nodes = fastcache.New(db.config.CleanCacheSize)
if nodes == nil && db.config.TrieCleanSize != 0 {
nodes = fastcache.New(db.config.TrieCleanSize)
}
if states == nil && db.config.StateCleanSize != 0 {
states = fastcache.New(db.config.StateCleanSize)
}
return &diskLayer{
root: root,
id: id,
db: db,
nodes: nodes,
states: states,
buffer: buffer,
}
}
@ -72,6 +87,13 @@ func (dl *diskLayer) parentLayer() layer {
return nil
}
// setGenerator links the given generator to disk layer, representing the
// associated state snapshot is not fully completed yet and the generation
// is potentially running in the background.
func (dl *diskLayer) setGenerator(generator *generator) {
dl.generator = generator
}
// isStale return whether this layer has become stale (was flattened across) or if
// it's still live.
func (dl *diskLayer) isStale() bool {
@ -168,8 +190,41 @@ func (dl *diskLayer) account(hash common.Hash, depth int) ([]byte, error) {
}
dirtyStateMissMeter.Mark(1)
// TODO(rjl493456442) support persistent state retrieval
return nil, errors.New("not supported")
// If the layer is being generated, ensure the requested account has
// already been covered by the generator.
marker := dl.genMarker()
if marker != nil && bytes.Compare(hash.Bytes(), marker) > 0 {
return nil, errNotCoveredYet
}
// Try to retrieve the account from the memory cache
if dl.states != nil {
if blob, found := dl.states.HasGet(nil, hash[:]); found {
cleanStateHitMeter.Mark(1)
cleanStateReadMeter.Mark(int64(len(blob)))
if len(blob) == 0 {
stateAccountInexMeter.Mark(1)
} else {
stateAccountExistMeter.Mark(1)
}
return blob, nil
}
cleanStateMissMeter.Mark(1)
}
// Try to retrieve the account from the disk.
blob = rawdb.ReadAccountSnapshot(dl.db.diskdb, hash)
if dl.states != nil {
dl.states.Set(hash[:], blob)
cleanStateWriteMeter.Mark(int64(len(blob)))
}
if len(blob) == 0 {
stateAccountInexMeter.Mark(1)
stateAccountInexDiskMeter.Mark(1)
} else {
stateAccountExistMeter.Mark(1)
stateAccountExistDiskMeter.Mark(1)
}
return blob, nil
}
// storage directly retrieves the storage data associated with a particular hash,
@ -203,8 +258,42 @@ func (dl *diskLayer) storage(accountHash, storageHash common.Hash, depth int) ([
}
dirtyStateMissMeter.Mark(1)
// TODO(rjl493456442) support persistent state retrieval
return nil, errors.New("not supported")
// If the layer is being generated, ensure the requested storage slot
// has already been covered by the generator.
key := append(accountHash[:], storageHash[:]...)
marker := dl.genMarker()
if marker != nil && bytes.Compare(key, marker) > 0 {
return nil, errNotCoveredYet
}
// Try to retrieve the storage slot from the memory cache
if dl.states != nil {
if blob, found := dl.states.HasGet(nil, key); found {
cleanStateHitMeter.Mark(1)
cleanStateReadMeter.Mark(int64(len(blob)))
if len(blob) == 0 {
stateStorageInexMeter.Mark(1)
} else {
stateStorageExistMeter.Mark(1)
}
return blob, nil
}
cleanStateMissMeter.Mark(1)
}
// Try to retrieve the account from the disk
blob := rawdb.ReadStorageSnapshot(dl.db.diskdb, accountHash, storageHash)
if dl.states != nil {
dl.states.Set(key, blob)
cleanStateWriteMeter.Mark(int64(len(blob)))
}
if len(blob) == 0 {
stateStorageInexMeter.Mark(1)
stateStorageInexDiskMeter.Mark(1)
} else {
stateStorageExistMeter.Mark(1)
stateStorageExistDiskMeter.Mark(1)
}
return blob, nil
}
// update implements the layer interface, returning a new diff layer on top
@ -267,13 +356,39 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
// Merge the trie nodes and flat states of the bottom-most diff layer into the
// buffer as the combined layer.
combined := dl.buffer.commit(bottom.nodes, bottom.states.stateSet)
// Terminate the background state snapshot generation before mutating the
// persistent state.
if combined.full() || force {
if err := combined.flush(dl.db.diskdb, dl.db.freezer, dl.nodes, bottom.stateID()); err != nil {
// Terminate the background state snapshot generator before flushing
// to prevent data race.
var progress []byte
if dl.generator != nil {
dl.generator.stop()
progress = dl.generator.progressMarker()
// If the snapshot has been fully generated, unset the generator
if progress == nil {
dl.setGenerator(nil)
} else {
log.Info("Paused snapshot generation")
}
}
// Flush the content in combined buffer. Any state data after the progress
// marker will be ignored, as the generator will pick it up later.
if err := combined.flush(bottom.root, dl.db.diskdb, dl.db.freezer, progress, dl.nodes, dl.states, bottom.stateID()); err != nil {
return nil, err
}
// Resume the background generation if it's not completed yet
if progress != nil {
dl.generator.run(bottom.root)
}
}
// Link the generator if snapshot is not yet completed
ndl := newDiskLayer(bottom.root, bottom.stateID(), dl.db, dl.nodes, dl.states, combined)
if dl.generator != nil {
ndl.setGenerator(dl.generator)
}
ndl := newDiskLayer(bottom.root, bottom.stateID(), dl.db, dl.nodes, combined)
// To remove outdated history objects from the end, we set the 'tail' parameter
// to 'oldest-1' due to the offset between the freezer index and the history ID.
if overflow {
@ -288,6 +403,7 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
// revert applies the given state history and return a reverted disk layer.
func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
start := time.Now()
if h.meta.root != dl.rootHash() {
return nil, errUnexpectedHistory
}
@ -321,15 +437,40 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
if err != nil {
return nil, err
}
} else {
batch := dl.db.diskdb.NewBatch()
writeNodes(batch, nodes, dl.nodes)
rawdb.WritePersistentStateID(batch, dl.id-1)
if err := batch.Write(); err != nil {
log.Crit("Failed to write states", "err", err)
ndl := newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.states, dl.buffer)
// Link the generator if it exists
if dl.generator != nil {
ndl.setGenerator(dl.generator)
}
log.Debug("Reverted data in write buffer", "oldroot", h.meta.root, "newroot", h.meta.parent, "elapsed", common.PrettyDuration(time.Since(start)))
return ndl, nil
}
return newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.buffer), nil
// Terminate the generation before writing any data into database
var progress []byte
if dl.generator != nil {
dl.generator.stop()
progress = dl.generator.progressMarker()
}
batch := dl.db.diskdb.NewBatch()
writeNodes(batch, nodes, dl.nodes)
// Provide the original values of modified accounts and storages for revert
writeStates(batch, progress, accounts, storages, dl.states)
rawdb.WritePersistentStateID(batch, dl.id-1)
rawdb.WriteSnapshotRoot(batch, h.meta.parent)
if err := batch.Write(); err != nil {
log.Crit("Failed to write states", "err", err)
}
// Link the generator and resume generation if the snapshot is not yet
// fully completed.
ndl := newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.states, dl.buffer)
if dl.generator != nil && !dl.generator.completed() {
ndl.generator = dl.generator
ndl.generator.run(h.meta.parent)
}
log.Debug("Reverted data in persistent state", "oldroot", h.meta.root, "newroot", h.meta.parent, "elapsed", common.PrettyDuration(time.Since(start)))
return ndl, nil
}
// size returns the approximate size of cached nodes in the disk layer.
@ -355,4 +496,16 @@ func (dl *diskLayer) resetCache() {
if dl.nodes != nil {
dl.nodes.Reset()
}
if dl.states != nil {
dl.states.Reset()
}
}
// genMarker returns the current state snapshot generation progress marker. If
// the state snapshot has already been fully generated, nil is returned.
func (dl *diskLayer) genMarker() []byte {
if dl.generator == nil {
return nil
}
return dl.generator.progressMarker()
}

View file

@ -39,4 +39,13 @@ var (
// errStateUnrecoverable is returned if state is required to be reverted to
// a destination without associated state history available.
errStateUnrecoverable = errors.New("state is unrecoverable")
// errNotCoveredYet is returned from data accessors if the underlying snapshot
// is being generated currently and the requested data item is not yet in the
// range of accounts covered.
errNotCoveredYet = errors.New("not covered yet")
// errNotConstructed is returned if the callers want to iterate the snapshot
// while the generation is not finished yet.
errNotConstructed = errors.New("snapshot is not constructed")
)

View file

@ -17,6 +17,8 @@
package pathdb
import (
"bytes"
"github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
@ -63,3 +65,69 @@ func writeNodes(batch ethdb.Batch, nodes map[common.Hash]map[string]*trienode.No
}
return total
}
// writeStates flushes state mutations into the provided database batch as a whole.
//
// This function assumes the background generator is already terminated and states
// before the supplied marker has been correctly generated.
//
// TODO(rjl493456442) do we really need this generation marker? The state updates
// after the marker can also be written and will be fixed by generator later if
// it's outdated.
func writeStates(batch ethdb.Batch, genMarker []byte, accountData map[common.Hash][]byte, storageData map[common.Hash]map[common.Hash][]byte, clean *fastcache.Cache) (int, int) {
var (
accounts int
slots int
)
for addrHash, blob := range accountData {
// Skip any account not yet covered by the snapshot. The account
// at the generation marker position (addrHash == genMarker[:common.HashLength])
// should still be updated, as it would be skipped in the next
// generation cycle.
if genMarker != nil && bytes.Compare(addrHash[:], genMarker) > 0 {
continue
}
accounts += 1
if len(blob) == 0 {
rawdb.DeleteAccountSnapshot(batch, addrHash)
if clean != nil {
clean.Set(addrHash[:], nil)
}
} else {
rawdb.WriteAccountSnapshot(batch, addrHash, blob)
if clean != nil {
clean.Set(addrHash[:], blob)
}
}
}
for addrHash, storages := range storageData {
// Skip any account not covered yet by the snapshot
if genMarker != nil && bytes.Compare(addrHash[:], genMarker) > 0 {
continue
}
midAccount := genMarker != nil && bytes.Equal(addrHash[:], genMarker[:common.HashLength])
for storageHash, blob := range storages {
// Skip any storage slot not yet covered by the snapshot. The storage slot
// at the generation marker position (addrHash == genMarker[:common.HashLength]
// and storageHash == genMarker[common.HashLength:]) should still be updated,
// as it would be skipped in the next generation cycle.
if midAccount && bytes.Compare(storageHash[:], genMarker[common.HashLength:]) > 0 {
continue
}
slots += 1
if len(blob) == 0 {
rawdb.DeleteStorageSnapshot(batch, addrHash, storageHash)
if clean != nil {
clean.Set(append(addrHash[:], storageHash[:]...), nil)
}
} else {
rawdb.WriteStorageSnapshot(batch, addrHash, storageHash, blob)
if clean != nil {
clean.Set(append(addrHash[:], storageHash[:]...), blob)
}
}
}
}
return accounts, slots
}

856
triedb/pathdb/generate.go Normal file
View file

@ -0,0 +1,856 @@
// Copyright 2019 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 pathdb
import (
"bytes"
"errors"
"fmt"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb/database"
)
var (
// accountCheckRange is the upper limit of the number of accounts involved in
// each range check. This is a value estimated based on experience. If this
// range is too large, the failure rate of range proof will increase. Otherwise,
// if the range is too small, the efficiency of the state recovery will decrease.
accountCheckRange = 128
// storageCheckRange is the upper limit of the number of storage slots involved
// in each range check. This is a value estimated based on experience. If this
// range is too large, the failure rate of range proof will increase. Otherwise,
// if the range is too small, the efficiency of the state recovery will decrease.
storageCheckRange = 1024
// errMissingTrie is returned if the target trie is missing while the generation
// is running. In this case the generation is aborted and wait the new signal.
errMissingTrie = errors.New("missing trie")
)
// diskReader is a wrapper of key-value store and implements database.NodeReader,
// providing a function for accessing persistent trie nodes in the disk
type diskReader struct{ db ethdb.KeyValueStore }
// Node retrieves the trie node blob with the provided trie identifier,
// node path and the corresponding node hash. No error will be returned
// if the node is not found.
func (r *diskReader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) {
if owner == (common.Hash{}) {
return rawdb.ReadAccountTrieNode(r.db, path), nil
}
return rawdb.ReadStorageTrieNode(r.db, owner, path), nil
}
// diskStore is a wrapper of key-value store and implements database.NodeDatabase.
// It's meant to be used for generating state snapshot from the trie data.
type diskStore struct {
db ethdb.KeyValueStore
}
// NodeReader returns a node reader associated with the specific state.
// An error will be returned if the specified state is not available.
func (s *diskStore) NodeReader(stateRoot common.Hash) (database.NodeReader, error) {
root := types.EmptyRootHash
if blob := rawdb.ReadAccountTrieNode(s.db, nil); len(blob) > 0 {
root = crypto.Keccak256Hash(blob)
}
if root != stateRoot {
return nil, fmt.Errorf("state %x is not available", stateRoot)
}
return &diskReader{s.db}, nil
}
// Generator is the struct for initial state snapshot generation. It is not thread-safe;
// the caller must manage concurrency issues themselves.
type generator struct {
noBuild bool // Flag indicating whether snapshot generation is permitted
running bool // Flag indicating whether the background generation is running
db ethdb.KeyValueStore // Key-value store containing the snapshot data
stats *generatorStats // Generation statistics used throughout the entire life cycle
abort chan chan struct{} // Notification channel to abort generating the snapshot in this layer
done chan struct{} // Notification channel when generation is done
progress []byte // Progress marker of the state generation, nil means it's completed
lock sync.RWMutex // Lock which protects the progress, only generator can mutate the progress
}
// newGenerator constructs the state snapshot generator.
//
// noBuild will be true if the background snapshot generation is not allowed,
// usually used in read-only mode.
//
// progress indicates the starting position for resuming snapshot generation.
// It must be provided even if generation is not allowed; otherwise, uncovered
// states may be exposed for serving.
func newGenerator(db ethdb.KeyValueStore, noBuild bool, progress []byte, stats *generatorStats) *generator {
if stats == nil {
stats = &generatorStats{start: time.Now()}
}
return &generator{
noBuild: noBuild,
progress: progress,
db: db,
stats: stats,
abort: make(chan chan struct{}),
done: make(chan struct{}),
}
}
// run starts the state snapshot generation in the background.
func (g *generator) run(root common.Hash) {
if g.noBuild {
log.Warn("Snapshot generation is not permitted")
return
}
if g.running {
g.stop()
log.Warn("Paused the leftover generation cycle")
}
g.running = true
go g.generate(newGeneratorContext(root, g.progress, g.db))
}
// stop terminates the background generation if it's actively running.
// The Recent generation progress being made will be saved before returning.
func (g *generator) stop() {
if !g.running {
log.Debug("Snapshot generation is not running")
return
}
ch := make(chan struct{})
g.abort <- ch
<-ch
g.running = false
}
// completed returns the flag indicating if the whole generation is done.
func (g *generator) completed() bool {
progress := g.progressMarker()
return progress == nil
}
// progressMarker returns the current generation progress marker. It may slightly
// lag behind the actual generation position, as the progress field is only updated
// when checkAndFlush is called. The only effect is that some generated states
// may be refused for serving.
func (g *generator) progressMarker() []byte {
g.lock.RLock()
defer g.lock.RUnlock()
return g.progress
}
// splitMarker is an internal helper which splits the generation progress marker
// into two parts.
func splitMarker(marker []byte) ([]byte, []byte) {
var accMarker []byte
if len(marker) > 0 {
accMarker = marker[:common.HashLength]
}
return accMarker, marker
}
// generateSnapshot regenerates a brand-new snapshot based on an existing state
// database and head block asynchronously. The snapshot is returned immediately
// and generation is continued in the background until done.
func generateSnapshot(triedb *Database, root common.Hash, noBuild bool) *diskLayer {
// Create a new disk layer with an initialized state marker at zero
var (
stats = &generatorStats{start: time.Now()}
genMarker = []byte{} // Initialized but empty!
)
dl := newDiskLayer(root, 0, triedb, nil, nil, newBuffer(triedb.config.WriteBufferSize, nil, nil, 0))
dl.setGenerator(newGenerator(triedb.diskdb, noBuild, genMarker, stats))
if !noBuild {
dl.generator.run(root)
log.Info("Started snapshot generation", "root", root)
}
return dl
}
// journalProgress persists the generator stats into the database to resume later.
func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorStats) {
// Write out the generator marker. Note it's a standalone disk layer generator
// which is not mixed with journal. It's ok if the generator is persisted while
// journal is not.
entry := journalGenerator{
Done: marker == nil,
Marker: marker,
}
if stats != nil {
entry.Accounts = stats.accounts
entry.Slots = stats.slots
entry.Storage = uint64(stats.storage)
}
blob, err := rlp.EncodeToBytes(entry)
if err != nil {
panic(err) // Cannot happen, here to catch dev errors
}
var logstr string
switch {
case marker == nil:
logstr = "done"
case bytes.Equal(marker, []byte{}):
logstr = "empty"
case len(marker) == common.HashLength:
logstr = fmt.Sprintf("%#x", marker)
default:
logstr = fmt.Sprintf("%#x:%#x", marker[:common.HashLength], marker[common.HashLength:])
}
log.Debug("Journalled generator progress", "progress", logstr)
rawdb.WriteSnapshotGenerator(db, blob)
}
// proofResult contains the output of range proving which can be used
// for further processing regardless if it is successful or not.
type proofResult struct {
keys [][]byte // The key set of all elements being iterated, even proving is failed
vals [][]byte // The val set of all elements being iterated, even proving is failed
diskMore bool // Set when the database has extra snapshot states since last iteration
trieMore bool // Set when the trie has extra snapshot states(only meaningful for successful proving)
proofErr error // Indicator whether the given state range is valid or not
tr *trie.Trie // The trie, in case the trie was resolved by the prover (may be nil)
}
// valid returns the indicator that range proof is successful or not.
func (result *proofResult) valid() bool {
return result.proofErr == nil
}
// last returns the last verified element key regardless of whether the range proof is
// successful or not. Nil is returned if nothing involved in the proving.
func (result *proofResult) last() []byte {
var last []byte
if len(result.keys) > 0 {
last = result.keys[len(result.keys)-1]
}
return last
}
// forEach iterates all the visited elements and applies the given callback on them.
// The iteration is aborted if the callback returns non-nil error.
func (result *proofResult) forEach(callback func(key []byte, val []byte) error) error {
for i := 0; i < len(result.keys); i++ {
key, val := result.keys[i], result.vals[i]
if err := callback(key, val); err != nil {
return err
}
}
return nil
}
// proveRange proves the snapshot segment with particular prefix is "valid".
// The iteration start point will be assigned if the iterator is restored from
// the last interruption. Max will be assigned in order to limit the maximum
// amount of data involved in each iteration.
//
// The proof result will be returned if the range proving is finished, otherwise
// the error will be returned to abort the entire procedure.
func (g *generator) proveRange(ctx *generatorContext, trieId *trie.ID, prefix []byte, kind string, origin []byte, max int, valueConvertFn func([]byte) ([]byte, error)) (*proofResult, error) {
var (
keys [][]byte
vals [][]byte
proof = rawdb.NewMemoryDatabase()
diskMore = false
iter = ctx.iterator(kind)
start = time.Now()
min = append(prefix, origin...)
)
for iter.Next() {
// Ensure the iterated item is always equal or larger than the given origin.
key := iter.Key()
if bytes.Compare(key, min) < 0 {
return nil, errors.New("invalid iteration position")
}
// Ensure the iterated item still fall in the specified prefix. If
// not which means the items in the specified area are all visited.
// Move the iterator a step back since we iterate one extra element
// out.
if !bytes.Equal(key[:len(prefix)], prefix) {
iter.Hold()
break
}
// Break if we've reached the max size, and signal that we're not
// done yet. Move the iterator a step back since we iterate one
// extra element out.
if len(keys) == max {
iter.Hold()
diskMore = true
break
}
keys = append(keys, common.CopyBytes(key[len(prefix):]))
if valueConvertFn == nil {
vals = append(vals, common.CopyBytes(iter.Value()))
} else {
val, err := valueConvertFn(iter.Value())
if err != nil {
// Special case, the state data is corrupted (invalid slim-format account),
// don't abort the entire procedure directly. Instead, let the fallback
// generation to heal the invalid data.
//
// Here append the original value to ensure that the number of key and
// value are aligned.
vals = append(vals, common.CopyBytes(iter.Value()))
log.Error("Failed to convert account state data", "err", err)
} else {
vals = append(vals, val)
}
}
}
// Update metrics for database iteration and merkle proving
if kind == snapStorage {
storageSnapReadCounter.Inc(time.Since(start).Nanoseconds())
} else {
accountSnapReadCounter.Inc(time.Since(start).Nanoseconds())
}
defer func(start time.Time) {
if kind == snapStorage {
storageProveCounter.Inc(time.Since(start).Nanoseconds())
} else {
accountProveCounter.Inc(time.Since(start).Nanoseconds())
}
}(time.Now())
// The snap state is exhausted, pass the entire key/val set for verification
root := trieId.Root
if origin == nil && !diskMore {
stackTr := trie.NewStackTrie(nil)
for i, key := range keys {
if err := stackTr.Update(key, vals[i]); err != nil {
return nil, err
}
}
if gotRoot := stackTr.Hash(); gotRoot != root {
return &proofResult{
keys: keys,
vals: vals,
proofErr: fmt.Errorf("wrong root: have %#x want %#x", gotRoot, root),
}, nil
}
return &proofResult{keys: keys, vals: vals}, nil
}
// Snap state is chunked, generate edge proofs for verification.
tr, err := trie.New(trieId, &diskStore{db: g.db})
if err != nil {
log.Info("Trie missing, snapshotting paused", "state", ctx.root, "kind", kind, "root", trieId.Root)
return nil, errMissingTrie
}
// Generate the Merkle proofs for the first and last element
if origin == nil {
origin = common.Hash{}.Bytes()
}
if err := tr.Prove(origin, proof); err != nil {
log.Debug("Failed to prove range", "kind", kind, "origin", origin, "err", err)
return &proofResult{
keys: keys,
vals: vals,
diskMore: diskMore,
proofErr: err,
tr: tr,
}, nil
}
if len(keys) > 0 {
if err := tr.Prove(keys[len(keys)-1], proof); err != nil {
log.Debug("Failed to prove range", "kind", kind, "last", keys[len(keys)-1], "err", err)
return &proofResult{
keys: keys,
vals: vals,
diskMore: diskMore,
proofErr: err,
tr: tr,
}, nil
}
}
// Verify the snapshot segment with range prover, ensure that all flat states
// in this range correspond to merkle trie.
cont, err := trie.VerifyRangeProof(root, origin, keys, vals, proof)
return &proofResult{
keys: keys,
vals: vals,
diskMore: diskMore,
trieMore: cont,
proofErr: err,
tr: tr},
nil
}
// onStateCallback is a function that is called by generateRange, when processing a range of
// accounts or storage slots. For each element, the callback is invoked.
//
// - If 'delete' is true, then this element (and potential slots) needs to be deleted from the snapshot.
// - If 'write' is true, then this element needs to be updated with the 'val'.
// - If 'write' is false, then this element is already correct, and needs no update.
// The 'val' is the canonical encoding of the value (not the slim format for accounts)
//
// However, for accounts, the storage trie of the account needs to be checked. Also,
// dangling storages(storage exists but the corresponding account is missing) need to
// be cleaned up.
type onStateCallback func(key []byte, val []byte, write bool, delete bool) error
// generateRange generates the state segment with particular prefix. Generation can
// either verify the correctness of existing state through range-proof and skip
// generation, or iterate trie to regenerate state on demand.
func (g *generator) generateRange(ctx *generatorContext, trieId *trie.ID, prefix []byte, kind string, origin []byte, max int, onState onStateCallback, valueConvertFn func([]byte) ([]byte, error)) (bool, []byte, error) {
// Use range prover to check the validity of the flat state in the range
result, err := g.proveRange(ctx, trieId, prefix, kind, origin, max, valueConvertFn)
if err != nil {
return false, nil, err
}
last := result.last()
// Construct contextual logger
logCtx := []interface{}{"kind", kind, "prefix", hexutil.Encode(prefix)}
if len(origin) > 0 {
logCtx = append(logCtx, "origin", hexutil.Encode(origin))
}
logger := log.New(logCtx...)
// The range prover says the range is correct, skip trie iteration
if result.valid() {
successfulRangeProofMeter.Mark(1)
logger.Trace("Proved state range", "last", hexutil.Encode(last))
// The verification is passed, process each state with the given
// callback function. If this state represents a contract, the
// corresponding storage check will be performed in the callback
if err := result.forEach(func(key []byte, val []byte) error { return onState(key, val, false, false) }); err != nil {
return false, nil, err
}
// Only abort the iteration when both database and trie are exhausted
return !result.diskMore && !result.trieMore, last, nil
}
logger.Trace("Detected outdated state range", "last", hexutil.Encode(last), "err", result.proofErr)
failedRangeProofMeter.Mark(1)
// Special case, the entire trie is missing. In the original trie scheme,
// all the duplicated subtries will be filtered out (only one copy of data
// will be stored). While in the snapshot model, all the storage tries
// belong to different contracts will be kept even they are duplicated.
// Track it to a certain extent remove the noise data used for statistics.
if origin == nil && last == nil {
meter := missallAccountMeter
if kind == snapStorage {
meter = missallStorageMeter
}
meter.Mark(1)
}
// We use the snap data to build up a cache which can be used by the
// main account trie as a primary lookup when resolving hashes
var resolver trie.NodeResolver
if len(result.keys) > 0 {
tr := trie.NewEmpty(nil)
for i, key := range result.keys {
tr.Update(key, result.vals[i])
}
_, nodes := tr.Commit(false)
hashSet := nodes.HashSet()
resolver = func(owner common.Hash, path []byte, hash common.Hash) []byte {
return hashSet[hash]
}
}
// Construct the trie for state iteration, reuse the trie
// if it's already opened with some nodes resolved.
tr := result.tr
if tr == nil {
tr, err = trie.New(trieId, &diskStore{db: g.db})
if err != nil {
log.Info("Trie missing, snapshotting paused", "state", ctx.root, "kind", kind, "root", trieId.Root)
return false, nil, errMissingTrie
}
}
var (
trieMore bool
kvkeys, kvvals = result.keys, result.vals
// counters
count = 0 // number of states delivered by iterator
created = 0 // states created from the trie
updated = 0 // states updated from the trie
deleted = 0 // states not in trie, but were in snapshot
untouched = 0 // states already correct
// timers
start = time.Now()
internal time.Duration
)
nodeIt, err := tr.NodeIterator(origin)
if err != nil {
return false, nil, err
}
nodeIt.AddResolver(resolver)
iter := trie.NewIterator(nodeIt)
for iter.Next() {
if last != nil && bytes.Compare(iter.Key, last) > 0 {
trieMore = true
break
}
count++
write := true
created++
for len(kvkeys) > 0 {
if cmp := bytes.Compare(kvkeys[0], iter.Key); cmp < 0 {
// delete the key
istart := time.Now()
if err := onState(kvkeys[0], nil, false, true); err != nil {
return false, nil, err
}
kvkeys = kvkeys[1:]
kvvals = kvvals[1:]
deleted++
internal += time.Since(istart)
continue
} else if cmp == 0 {
// the snapshot key can be overwritten
created--
if write = !bytes.Equal(kvvals[0], iter.Value); write {
updated++
} else {
untouched++
}
kvkeys = kvkeys[1:]
kvvals = kvvals[1:]
}
break
}
istart := time.Now()
if err := onState(iter.Key, iter.Value, write, false); err != nil {
return false, nil, err
}
internal += time.Since(istart)
}
if iter.Err != nil {
// Trie errors should never happen. Still, in case of a bug, expose the
// error here, as the outer code will presume errors are interrupts, not
// some deeper issues.
log.Error("State snapshotter failed to iterate trie", "err", iter.Err)
return false, nil, iter.Err
}
// Delete all stale snapshot states remaining
istart := time.Now()
for _, key := range kvkeys {
if err := onState(key, nil, false, true); err != nil {
return false, nil, err
}
deleted += 1
}
internal += time.Since(istart)
// Update metrics for counting trie iteration
if kind == snapStorage {
storageTrieReadCounter.Inc((time.Since(start) - internal).Nanoseconds())
} else {
accountTrieReadCounter.Inc((time.Since(start) - internal).Nanoseconds())
}
logger.Trace("Regenerated state range", "root", trieId.Root, "last", hexutil.Encode(last),
"count", count, "created", created, "updated", updated, "untouched", untouched, "deleted", deleted)
// If there are either more trie items, or there are more snap items
// (in the next segment), then we need to keep working
return !trieMore && !result.diskMore, last, nil
}
// checkAndFlush checks if an interruption signal is received or the
// batch size has exceeded the allowance.
func (g *generator) checkAndFlush(ctx *generatorContext, current []byte) error {
var abort chan struct{}
select {
case abort = <-g.abort:
default:
}
if ctx.batch.ValueSize() > ethdb.IdealBatchSize || abort != nil {
if bytes.Compare(current, g.progress) < 0 {
log.Error("Snapshot generator went backwards", "current", fmt.Sprintf("%x", current), "genMarker", fmt.Sprintf("%x", g.progress))
}
// Persist the progress marker regardless of whether the batch is empty or not.
// It may happen that all the flat states in the database are correct, so the
// generator indeed makes progress even if there is nothing to commit.
journalProgress(ctx.batch, current, g.stats)
// Flush out the database writes atomically
if err := ctx.batch.Write(); err != nil {
return err
}
ctx.batch.Reset()
// Update the generation progress marker
g.lock.Lock()
g.progress = current
g.lock.Unlock()
// Abort the generation if it's required
if abort != nil {
g.stats.log("Aborting snapshot generation", ctx.root, g.progress)
return newAbortErr(abort) // bubble up an error for interruption
}
// Don't hold the iterators too long, release them to let compactor works
ctx.reopenIterator(snapAccount)
ctx.reopenIterator(snapStorage)
}
if time.Since(ctx.logged) > 8*time.Second {
g.stats.log("Generating snapshot", ctx.root, g.progress)
ctx.logged = time.Now()
}
return nil
}
// generateStorages generates the missing storage slots of the specific contract.
// It's supposed to restart the generation from the given origin position.
func (g *generator) generateStorages(ctx *generatorContext, account common.Hash, storageRoot common.Hash, storeMarker []byte) error {
onStorage := func(key []byte, val []byte, write bool, delete bool) error {
defer func(start time.Time) {
storageWriteCounter.Inc(time.Since(start).Nanoseconds())
}(time.Now())
if delete {
rawdb.DeleteStorageSnapshot(ctx.batch, account, common.BytesToHash(key))
wipedStorageMeter.Mark(1)
return nil
}
if write {
rawdb.WriteStorageSnapshot(ctx.batch, account, common.BytesToHash(key), val)
generatedStorageMeter.Mark(1)
} else {
recoveredStorageMeter.Mark(1)
}
g.stats.storage += common.StorageSize(1 + 2*common.HashLength + len(val))
g.stats.slots++
// If we've exceeded our batch allowance or termination was requested, flush to disk
if err := g.checkAndFlush(ctx, append(account[:], key...)); err != nil {
return err
}
return nil
}
// Loop for re-generating the missing storage slots.
var origin = common.CopyBytes(storeMarker)
for {
id := trie.StorageTrieID(ctx.root, account, storageRoot)
exhausted, last, err := g.generateRange(ctx, id, append(rawdb.SnapshotStoragePrefix, account.Bytes()...), snapStorage, origin, storageCheckRange, onStorage, nil)
if err != nil {
return err // The procedure it aborted, either by external signal or internal error.
}
// Abort the procedure if the entire contract storage is generated
if exhausted {
break
}
if origin = increaseKey(last); origin == nil {
break // special case, the last is 0xffffffff...fff
}
}
return nil
}
// generateAccounts generates the missing snapshot accounts as well as their
// storage slots in the main trie. It's supposed to restart the generation
// from the given origin position.
func (g *generator) generateAccounts(ctx *generatorContext, accMarker []byte) error {
onAccount := func(key []byte, val []byte, write bool, delete bool) error {
// Make sure to clear all dangling storages before this account
account := common.BytesToHash(key)
g.stats.dangling += ctx.removeStorageBefore(account)
start := time.Now()
if delete {
rawdb.DeleteAccountSnapshot(ctx.batch, account)
wipedAccountMeter.Mark(1)
accountWriteCounter.Inc(time.Since(start).Nanoseconds())
ctx.removeStorageAt(account)
return nil
}
// Retrieve the current account and flatten it into the internal format
var acc types.StateAccount
if err := rlp.DecodeBytes(val, &acc); err != nil {
log.Crit("Invalid account encountered during snapshot creation", "err", err)
}
// If the account is not yet in-progress, write it out
if accMarker == nil || !bytes.Equal(account[:], accMarker) {
dataLen := len(val) // Approximate size, saves us a round of RLP-encoding
if !write {
if bytes.Equal(acc.CodeHash, types.EmptyCodeHash[:]) {
dataLen -= 32
}
if acc.Root == types.EmptyRootHash {
dataLen -= 32
}
recoveredAccountMeter.Mark(1)
} else {
data := types.SlimAccountRLP(acc)
dataLen = len(data)
rawdb.WriteAccountSnapshot(ctx.batch, account, data)
generatedAccountMeter.Mark(1)
}
g.stats.storage += common.StorageSize(1 + common.HashLength + dataLen)
g.stats.accounts++
}
// If the snap generation goes here after interrupted, genMarker may go backward
// when last genMarker is consisted of accountHash and storageHash
marker := account[:]
if accMarker != nil && bytes.Equal(marker, accMarker) && len(g.progress) > common.HashLength {
marker = g.progress
}
// If we've exceeded our batch allowance or termination was requested, flush to disk
if err := g.checkAndFlush(ctx, marker); err != nil {
return err
}
accountWriteCounter.Inc(time.Since(start).Nanoseconds()) // let's count flush time as well
// If the iterated account is the contract, create a further loop to
// verify or regenerate the contract storage.
if acc.Root == types.EmptyRootHash {
ctx.removeStorageAt(account)
} else {
var storeMarker []byte
if accMarker != nil && bytes.Equal(account[:], accMarker) && len(g.progress) > common.HashLength {
storeMarker = g.progress[common.HashLength:]
}
if err := g.generateStorages(ctx, account, acc.Root, storeMarker); err != nil {
return err
}
}
// Some account processed, unmark the marker
accMarker = nil
return nil
}
origin := common.CopyBytes(accMarker)
for {
id := trie.StateTrieID(ctx.root)
exhausted, last, err := g.generateRange(ctx, id, rawdb.SnapshotAccountPrefix, snapAccount, origin, accountCheckRange, onAccount, types.FullAccountRLP)
if err != nil {
return err // The procedure it aborted, either by external signal or internal error.
}
origin = increaseKey(last)
// Last step, cleanup the storages after the last account.
// All the left storages should be treated as dangling.
if origin == nil || exhausted {
g.stats.dangling += ctx.removeRemainingStorage()
break
}
}
return nil
}
// generate is a background thread that iterates over the state and storage tries,
// constructing the state snapshot. All the arguments are purely for statistics
// gathering and logging, since the method surfs the blocks as they arrive, often
// being restarted.
func (g *generator) generate(ctx *generatorContext) {
g.stats.log("Resuming snapshot generation", ctx.root, g.progress)
defer ctx.close()
// Persist the initial marker and state snapshot root if progress is none
if len(g.progress) == 0 {
batch := g.db.NewBatch()
rawdb.WriteSnapshotRoot(batch, ctx.root)
journalProgress(batch, g.progress, g.stats)
if err := batch.Write(); err != nil {
log.Crit("Failed to write initialized state marker", "err", err)
}
}
// Initialize the global generator context. The snapshot iterators are
// opened at the interrupted position because the assumption is held
// that all the snapshot data are generated correctly before the marker.
// Even if the snapshot data is updated during the interruption (before
// or at the marker), the assumption is still held.
// For the account or storage slot at the interruption, they will be
// processed twice by the generator(they are already processed in the
// last run) but it's fine.
var (
accMarker, _ = splitMarker(g.progress)
abort chan struct{}
)
if err := g.generateAccounts(ctx, accMarker); err != nil {
// Extract the received interruption signal if exists
var aerr *abortErr
if errors.As(err, &aerr) {
abort = aerr.abort
}
// Aborted by internal error, wait the signal
if abort == nil {
abort = <-g.abort
}
close(abort)
return
}
// Snapshot fully generated, set the marker to nil.
// Note even there is nothing to commit, persist the
// generator anyway to mark the snapshot is complete.
journalProgress(ctx.batch, nil, g.stats)
if err := ctx.batch.Write(); err != nil {
log.Error("Failed to flush batch", "err", err)
abort = <-g.abort
close(abort)
return
}
ctx.batch.Reset()
log.Info("Generated snapshot", "accounts", g.stats.accounts, "slots", g.stats.slots,
"storage", g.stats.storage, "dangling", g.stats.dangling, "elapsed", common.PrettyDuration(time.Since(g.stats.start)))
// Update the generation progress marker
g.lock.Lock()
g.progress = nil
g.lock.Unlock()
close(g.done)
// Someone will be looking for us, wait it out
abort = <-g.abort
close(abort)
}
// increaseKey increase the input key by one bit. Return nil if the entire
// addition operation overflows.
func increaseKey(key []byte) []byte {
for i := len(key) - 1; i >= 0; i-- {
key[i]++
if key[i] != 0x0 {
return key
}
}
return nil
}
// abortErr wraps an interruption signal received to represent the
// generation is aborted by external processes.
type abortErr struct {
abort chan struct{}
}
func newAbortErr(abort chan struct{}) error {
return &abortErr{abort: abort}
}
func (err *abortErr) Error() string {
return "aborted"
}

View file

@ -0,0 +1,766 @@
// Copyright 2024 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 pathdb
import (
"fmt"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/testrand"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/holiman/uint256"
)
func hashData(input []byte) common.Hash {
return crypto.Keccak256Hash(input)
}
type genTester struct {
diskdb ethdb.Database
db *Database
acctTrie *trie.Trie
nodes *trienode.MergedNodeSet
states *StateSetWithOrigin
}
func newGenTester() *genTester {
disk := rawdb.NewMemoryDatabase()
config := *Defaults
config.SnapshotNoBuild = true // no background generation
db := New(disk, &config, false)
tr, _ := trie.New(trie.StateTrieID(types.EmptyRootHash), db)
return &genTester{
diskdb: disk,
db: db,
acctTrie: tr,
nodes: trienode.NewMergedNodeSet(),
states: NewStateSetWithOrigin(nil, nil, nil, nil, false),
}
}
func (t *genTester) addTrieAccount(acckey string, acc *types.StateAccount) {
var (
addr = common.BytesToAddress([]byte(acckey))
key = hashData([]byte(acckey))
val, _ = rlp.EncodeToBytes(acc)
)
t.acctTrie.MustUpdate(key.Bytes(), val)
t.states.accountData[key] = val
t.states.accountOrigin[addr] = nil
}
func (t *genTester) addSnapAccount(acckey string, acc *types.StateAccount) {
key := hashData([]byte(acckey))
rawdb.WriteAccountSnapshot(t.diskdb, key, types.SlimAccountRLP(*acc))
}
func (t *genTester) addAccount(acckey string, acc *types.StateAccount) {
t.addTrieAccount(acckey, acc)
t.addSnapAccount(acckey, acc)
}
func (t *genTester) addSnapStorage(accKey string, keys []string, vals []string) {
accHash := hashData([]byte(accKey))
for i, key := range keys {
rawdb.WriteStorageSnapshot(t.diskdb, accHash, hashData([]byte(key)), []byte(vals[i]))
}
}
func (t *genTester) makeStorageTrie(accKey string, keys []string, vals []string, commit bool) common.Hash {
var (
owner = hashData([]byte(accKey))
addr = common.BytesToAddress([]byte(accKey))
id = trie.StorageTrieID(types.EmptyRootHash, owner, types.EmptyRootHash)
tr, _ = trie.New(id, t.db)
storages = make(map[common.Hash][]byte)
storageOrigins = make(map[common.Hash][]byte)
)
for i, k := range keys {
key := hashData([]byte(k))
tr.MustUpdate(key.Bytes(), []byte(vals[i]))
storages[key] = []byte(vals[i])
storageOrigins[key] = nil
}
if !commit {
return tr.Hash()
}
root, nodes := tr.Commit(false)
if nodes != nil {
t.nodes.Merge(nodes)
}
t.states.storageData[owner] = storages
t.states.storageOrigin[addr] = storageOrigins
return root
}
func (t *genTester) Commit() common.Hash {
root, nodes := t.acctTrie.Commit(true)
if nodes != nil {
t.nodes.Merge(nodes)
}
t.db.Update(root, types.EmptyRootHash, 0, t.nodes, t.states)
t.db.Commit(root, false)
return root
}
func (t *genTester) CommitAndGenerate() (common.Hash, *diskLayer) {
root := t.Commit()
dl := generateSnapshot(t.db, root, false)
return root, dl
}
// Tests that snapshot generation from an empty database.
func TestGeneration(t *testing.T) {
helper := newGenTester()
stRoot := helper.makeStorageTrie("", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, false)
helper.addTrieAccount("acc-1", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addTrieAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addTrieAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.makeStorageTrie("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.makeStorageTrie("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
root, dl := helper.CommitAndGenerate()
if have, want := root, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"); have != want {
t.Fatalf("have %#x want %#x", have, want)
}
select {
case <-dl.generator.done:
// Snapshot generation succeeded
case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
// TODO(rjl493456442) enable the snapshot tests
// checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
dl.generator.stop()
}
// Tests that snapshot generation with existent flat state, where the flat state
// contains some errors:
// - the contract with empty storage root but has storage entries in the disk
// - the contract with non empty storage root but empty storage slots
// - the contract(non-empty storage) misses some storage slots
// - miss in the beginning
// - miss in the middle
// - miss in the end
//
// - the contract(non-empty storage) has wrong storage slots
// - wrong slots in the beginning
// - wrong slots in the middle
// - wrong slots in the end
//
// - the contract(non-empty storage) has extra storage slots
// - extra slots in the beginning
// - extra slots in the middle
// - extra slots in the end
func TestGenerateExistentStateWithWrongStorage(t *testing.T) {
helper := newGenTester()
// Account one, empty storage trie root but non-empty flat states
helper.addAccount("acc-1", &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
// Account two, non-empty storage trie root but empty flat states
stRoot := helper.makeStorageTrie("acc-2", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
// Miss slots
{
// Account three, non-empty root but misses slots in the beginning
helper.makeStorageTrie("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addSnapStorage("acc-3", []string{"key-2", "key-3"}, []string{"val-2", "val-3"})
// Account four, non-empty root but misses slots in the middle
helper.makeStorageTrie("acc-4", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-4", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addSnapStorage("acc-4", []string{"key-1", "key-3"}, []string{"val-1", "val-3"})
// Account five, non-empty root but misses slots in the end
helper.makeStorageTrie("acc-5", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-5", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addSnapStorage("acc-5", []string{"key-1", "key-2"}, []string{"val-1", "val-2"})
}
// Wrong storage slots
{
// Account six, non-empty root but wrong slots in the beginning
helper.makeStorageTrie("acc-6", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-6", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addSnapStorage("acc-6", []string{"key-1", "key-2", "key-3"}, []string{"badval-1", "val-2", "val-3"})
// Account seven, non-empty root but wrong slots in the middle
helper.makeStorageTrie("acc-7", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-7", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addSnapStorage("acc-7", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "badval-2", "val-3"})
// Account eight, non-empty root but wrong slots in the end
helper.makeStorageTrie("acc-8", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-8", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addSnapStorage("acc-8", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "badval-3"})
// Account 9, non-empty root but rotated slots
helper.makeStorageTrie("acc-9", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-9", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addSnapStorage("acc-9", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-3", "val-2"})
}
// Extra storage slots
{
// Account 10, non-empty root but extra slots in the beginning
helper.makeStorageTrie("acc-10", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-10", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addSnapStorage("acc-10", []string{"key-0", "key-1", "key-2", "key-3"}, []string{"val-0", "val-1", "val-2", "val-3"})
// Account 11, non-empty root but extra slots in the middle
helper.makeStorageTrie("acc-11", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-11", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addSnapStorage("acc-11", []string{"key-1", "key-2", "key-2-1", "key-3"}, []string{"val-1", "val-2", "val-2-1", "val-3"})
// Account 12, non-empty root but extra slots in the end
helper.makeStorageTrie("acc-12", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-12", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addSnapStorage("acc-12", []string{"key-1", "key-2", "key-3", "key-4"}, []string{"val-1", "val-2", "val-3", "val-4"})
}
root, dl := helper.CommitAndGenerate()
t.Logf("Root: %#x\n", root) // Root = 0x8746cce9fd9c658b2cfd639878ed6584b7a2b3e73bb40f607fcfa156002429a0
select {
case <-dl.generator.done:
// Snapshot generation succeeded
case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
// TODO(rjl493456442) enable the snapshot tests
// checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
dl.generator.stop()
}
// Tests that snapshot generation with existent flat state, where the flat state
// contains some errors:
// - miss accounts
// - wrong accounts
// - extra accounts
func TestGenerateExistentStateWithWrongAccounts(t *testing.T) {
helper := newGenTester()
// Trie accounts [acc-1, acc-2, acc-3, acc-4, acc-6]
helper.makeStorageTrie("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.makeStorageTrie("acc-2", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.makeStorageTrie("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.makeStorageTrie("acc-4", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
stRoot := helper.makeStorageTrie("acc-6", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
// Missing accounts, only in the trie
{
helper.addTrieAccount("acc-1", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // Beginning
helper.addTrieAccount("acc-4", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // Middle
helper.addTrieAccount("acc-6", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // End
}
// Wrong accounts
{
helper.addTrieAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addSnapAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: common.Hex2Bytes("0x1234")})
helper.addTrieAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addSnapAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()})
}
// Extra accounts, only in the snap
{
helper.addSnapAccount("acc-0", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // before the beginning
helper.addSnapAccount("acc-5", &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: common.Hex2Bytes("0x1234")}) // Middle
helper.addSnapAccount("acc-7", &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // after the end
}
root, dl := helper.CommitAndGenerate()
t.Logf("Root: %#x\n", root) // Root = 0x825891472281463511e7ebcc7f109e4f9200c20fa384754e11fd605cd98464e8
select {
case <-dl.generator.done:
// Snapshot generation succeeded
case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
// TODO(rjl493456442) enable the snapshot tests
// checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
dl.generator.stop()
}
func TestGenerateCorruptAccountTrie(t *testing.T) {
helper := newGenTester()
helper.addTrieAccount("acc-1", &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0xc7a30f39aff471c95d8a837497ad0e49b65be475cc0953540f80cfcdbdcd9074
helper.addTrieAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
helper.addTrieAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(3), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x19ead688e907b0fab07176120dceec244a72aff2f0aa51e8b827584e378772f4
root := helper.Commit() // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978
// Delete an account trie node and ensure the generator chokes
path := []byte{0xc}
if !rawdb.HasAccountTrieNode(helper.diskdb, path) {
t.Logf("Invalid node path to delete, %v", path)
}
rawdb.DeleteAccountTrieNode(helper.diskdb, path)
helper.db.tree.bottom().resetCache()
dl := generateSnapshot(helper.db, root, false)
select {
case <-dl.generator.done:
// Snapshot generation succeeded
t.Errorf("Snapshot generated against corrupt account trie")
case <-time.After(time.Second):
// Not generated fast enough, hopefully blocked inside on missing trie node fail
}
// Signal abortion to the generator and wait for it to tear down
dl.generator.stop()
}
func TestGenerateMissingStorageTrie(t *testing.T) {
var (
acc1 = hashData([]byte("acc-1"))
acc3 = hashData([]byte("acc-3"))
helper = newGenTester()
)
stRoot := helper.makeStorageTrie("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
helper.addTrieAccount("acc-1", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
helper.addTrieAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
stRoot = helper.makeStorageTrie("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
root := helper.Commit()
// Delete storage trie root of account one and three.
rawdb.DeleteStorageTrieNode(helper.diskdb, acc1, nil)
rawdb.DeleteStorageTrieNode(helper.diskdb, acc3, nil)
helper.db.tree.bottom().resetCache()
dl := generateSnapshot(helper.db, root, false)
select {
case <-dl.generator.done:
// Snapshot generation succeeded
t.Errorf("Snapshot generated against corrupt storage trie")
case <-time.After(time.Second):
// Not generated fast enough, hopefully blocked inside on missing trie node fail
}
// Signal abortion to the generator and wait for it to tear down
dl.generator.stop()
}
func TestGenerateCorruptStorageTrie(t *testing.T) {
helper := newGenTester()
stRoot := helper.makeStorageTrie("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
helper.addTrieAccount("acc-1", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
helper.addTrieAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
stRoot = helper.makeStorageTrie("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
root := helper.Commit()
// Delete a node in the storage trie.
path := []byte{0x4}
if !rawdb.HasStorageTrieNode(helper.diskdb, hashData([]byte("acc-1")), path) {
t.Logf("Invalid node path to delete, %v", path)
}
rawdb.DeleteStorageTrieNode(helper.diskdb, hashData([]byte("acc-1")), []byte{0x4})
if !rawdb.HasStorageTrieNode(helper.diskdb, hashData([]byte("acc-3")), path) {
t.Logf("Invalid node path to delete, %v", path)
}
rawdb.DeleteStorageTrieNode(helper.diskdb, hashData([]byte("acc-3")), []byte{0x4})
helper.db.tree.bottom().resetCache()
dl := generateSnapshot(helper.db, root, false)
select {
case <-dl.generator.done:
// Snapshot generation succeeded
t.Errorf("Snapshot generated against corrupt storage trie")
case <-time.After(time.Second):
// Not generated fast enough, hopefully blocked inside on missing trie node fail
}
// Signal abortion to the generator and wait for it to tear down
dl.generator.stop()
}
func TestGenerateWithExtraAccounts(t *testing.T) {
helper := newGenTester()
// Account one in the trie
stRoot := helper.makeStorageTrie("acc-1",
[]string{"key-1", "key-2", "key-3", "key-4", "key-5"},
[]string{"val-1", "val-2", "val-3", "val-4", "val-5"},
true,
)
acc := &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
helper.acctTrie.MustUpdate(hashData([]byte("acc-1")).Bytes(), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
// Identical in the snap
key := hashData([]byte("acc-1"))
rawdb.WriteAccountSnapshot(helper.diskdb, key, val)
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-1")), []byte("val-1"))
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-2")), []byte("val-2"))
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-3")), []byte("val-3"))
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-4")), []byte("val-4"))
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-5")), []byte("val-5"))
// Account two exists only in the snapshot
stRoot = helper.makeStorageTrie("acc-2",
[]string{"key-1", "key-2", "key-3", "key-4", "key-5"},
[]string{"val-1", "val-2", "val-3", "val-4", "val-5"},
true,
)
acc = &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}
val, _ = rlp.EncodeToBytes(acc)
key = hashData([]byte("acc-2"))
rawdb.WriteAccountSnapshot(helper.diskdb, key, val)
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("b-key-1")), []byte("b-val-1"))
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("b-key-2")), []byte("b-val-2"))
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("b-key-3")), []byte("b-val-3"))
root := helper.Commit()
// To verify the test: If we now inspect the snap db, there should exist extraneous storage items
if data := rawdb.ReadStorageSnapshot(helper.diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data == nil {
t.Fatalf("expected snap storage to exist")
}
dl := generateSnapshot(helper.db, root, false)
select {
case <-dl.generator.done:
// Snapshot generation succeeded
case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
// TODO(rjl493456442) enable the snapshot tests
// checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
dl.generator.stop()
// If we now inspect the snap db, there should exist no extraneous storage items
if data := rawdb.ReadStorageSnapshot(helper.diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil {
t.Fatalf("expected slot to be removed, got %v", string(data))
}
}
func TestGenerateWithManyExtraAccounts(t *testing.T) {
helper := newGenTester()
// Account one in the trie
stRoot := helper.makeStorageTrie("acc-1",
[]string{"key-1", "key-2", "key-3"},
[]string{"val-1", "val-2", "val-3"},
true,
)
acc := &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
helper.acctTrie.MustUpdate(hashData([]byte("acc-1")).Bytes(), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
// Identical in the snap
key := hashData([]byte("acc-1"))
rawdb.WriteAccountSnapshot(helper.diskdb, key, val)
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-1")), []byte("val-1"))
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-2")), []byte("val-2"))
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-3")), []byte("val-3"))
// 100 accounts exist only in snapshot
for i := 0; i < 1000; i++ {
acc := &types.StateAccount{Balance: uint256.NewInt(uint64(i)), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
key := hashData([]byte(fmt.Sprintf("acc-%d", i)))
rawdb.WriteAccountSnapshot(helper.diskdb, key, val)
}
_, dl := helper.CommitAndGenerate()
select {
case <-dl.generator.done:
// Snapshot generation succeeded
case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
// TODO(rjl493456442) enable the snapshot tests
// checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
dl.generator.stop()
}
func TestGenerateWithExtraBeforeAndAfter(t *testing.T) {
helper := newGenTester()
acc := &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
acctHashA := hashData([]byte("acc-1"))
acctHashB := hashData([]byte("acc-2"))
helper.acctTrie.MustUpdate(acctHashA.Bytes(), val)
helper.acctTrie.MustUpdate(acctHashB.Bytes(), val)
rawdb.WriteAccountSnapshot(helper.diskdb, acctHashA, val)
rawdb.WriteAccountSnapshot(helper.diskdb, acctHashB, val)
for i := 0; i < 16; i++ {
rawdb.WriteAccountSnapshot(helper.diskdb, common.Hash{byte(i)}, val)
}
_, dl := helper.CommitAndGenerate()
select {
case <-dl.generator.done:
// Snapshot generation succeeded
case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
// TODO(rjl493456442) enable the snapshot tests
// checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
dl.generator.stop()
}
func TestGenerateWithMalformedStateData(t *testing.T) {
helper := newGenTester()
acctHash := hashData([]byte("acc"))
acc := &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}
val, _ := rlp.EncodeToBytes(acc)
helper.acctTrie.MustUpdate(acctHash.Bytes(), val)
junk := make([]byte, 100)
copy(junk, []byte{0xde, 0xad})
rawdb.WriteAccountSnapshot(helper.diskdb, acctHash, junk)
for i := 0; i < 16; i++ {
rawdb.WriteAccountSnapshot(helper.diskdb, common.Hash{byte(i)}, junk)
}
_, dl := helper.CommitAndGenerate()
select {
case <-dl.generator.done:
// Snapshot generation succeeded
case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
// TODO(rjl493456442) enable the snapshot tests
// checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
dl.generator.stop()
}
func TestGenerateFromEmptySnap(t *testing.T) {
helper := newGenTester()
for i := 0; i < 400; i++ {
stRoot := helper.makeStorageTrie(fmt.Sprintf("acc-%d", i), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount(fmt.Sprintf("acc-%d", i), &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
}
root, snap := helper.CommitAndGenerate()
t.Logf("Root: %#x\n", root) // Root: 0x6f7af6d2e1a1bf2b84a3beb3f8b64388465fbc1e274ca5d5d3fc787ca78f59e4
select {
case <-snap.generator.done:
// Snapshot generation succeeded
case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
// TODO(rjl493456442) enable the snapshot tests
// checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
snap.generator.stop()
}
func TestGenerateWithIncompleteStorage(t *testing.T) {
helper := newGenTester()
stKeys := []string{"1", "2", "3", "4", "5", "6", "7", "8"}
stVals := []string{"v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8"}
// We add 8 accounts, each one is missing exactly one of the storage slots. This means
// we don't have to order the keys and figure out exactly which hash-key winds up
// on the sensitive spots at the boundaries
for i := 0; i < 8; i++ {
accKey := fmt.Sprintf("acc-%d", i)
stRoot := helper.makeStorageTrie(accKey, stKeys, stVals, true)
helper.addAccount(accKey, &types.StateAccount{Balance: uint256.NewInt(uint64(i)), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
var moddedKeys []string
var moddedVals []string
for ii := 0; ii < 8; ii++ {
if ii != i {
moddedKeys = append(moddedKeys, stKeys[ii])
moddedVals = append(moddedVals, stVals[ii])
}
}
helper.addSnapStorage(accKey, moddedKeys, moddedVals)
}
root, dl := helper.CommitAndGenerate()
t.Logf("Root: %#x\n", root) // Root: 0xca73f6f05ba4ca3024ef340ef3dfca8fdabc1b677ff13f5a9571fd49c16e67ff
select {
case <-dl.generator.done:
// Snapshot generation succeeded
case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
// TODO(rjl493456442) enable the snapshot tests
// checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
dl.generator.stop()
}
func incKey(key []byte) []byte {
for i := len(key) - 1; i >= 0; i-- {
key[i]++
if key[i] != 0x0 {
break
}
}
return key
}
func decKey(key []byte) []byte {
for i := len(key) - 1; i >= 0; i-- {
key[i]--
if key[i] != 0xff {
break
}
}
return key
}
func populateDangling(disk ethdb.KeyValueStore) {
populate := func(accountHash common.Hash, keys []string, vals []string) {
for i, key := range keys {
rawdb.WriteStorageSnapshot(disk, accountHash, hashData([]byte(key)), []byte(vals[i]))
}
}
// Dangling storages of the "first" account
populate(common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
// Dangling storages of the "last" account
populate(common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
// Dangling storages around the account 1
hash := decKey(hashData([]byte("acc-1")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
hash = incKey(hashData([]byte("acc-1")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
// Dangling storages around the account 2
hash = decKey(hashData([]byte("acc-2")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
hash = incKey(hashData([]byte("acc-2")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
// Dangling storages around the account 3
hash = decKey(hashData([]byte("acc-3")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
hash = incKey(hashData([]byte("acc-3")).Bytes())
populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
// Dangling storages of the random account
populate(testrand.Hash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
populate(testrand.Hash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
populate(testrand.Hash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
}
func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) {
var helper = newGenTester()
stRoot := helper.makeStorageTrie("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-1", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()})
helper.makeStorageTrie("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
helper.addSnapStorage("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
populateDangling(helper.diskdb)
_, dl := helper.CommitAndGenerate()
select {
case <-dl.generator.done:
// Snapshot generation succeeded
case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
// TODO(rjl493456442) enable the snapshot tests
// checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
dl.generator.stop()
}
func TestGenerateBrokenSnapshotWithDanglingStorage(t *testing.T) {
var helper = newGenTester()
stRoot := helper.makeStorageTrie("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-1", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
helper.addTrieAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()})
helper.makeStorageTrie("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()})
populateDangling(helper.diskdb)
_, dl := helper.CommitAndGenerate()
select {
case <-dl.generator.done:
// Snapshot generation succeeded
case <-time.After(3 * time.Second):
t.Errorf("Snapshot generation failed")
}
// TODO(rjl493456442) enable the snapshot tests
// checkSnapRoot(t, snap, root)
// Signal abortion to the generator and wait for it to tear down
dl.generator.stop()
}

View file

@ -91,15 +91,14 @@ type diffAccountIterator struct {
}
// newDiffAccountIterator creates an account iterator over the given state set.
func newDiffAccountIterator(seek common.Hash, states *stateSet, fn loadAccount) AccountIterator {
func newDiffAccountIterator(seek common.Hash, accountList []common.Hash, fn loadAccount) AccountIterator {
// Seek out the requested starting account
hashes := states.accountList()
index := sort.Search(len(hashes), func(i int) bool {
return bytes.Compare(seek[:], hashes[i][:]) <= 0
index := sort.Search(len(accountList), func(i int) bool {
return bytes.Compare(seek[:], accountList[i][:]) <= 0
})
// Assemble and returned the already seeked iterator
return &diffAccountIterator{
keys: hashes[index:],
keys: accountList[index:],
loadFn: fn,
}
}
@ -236,15 +235,14 @@ type diffStorageIterator struct {
}
// newDiffStorageIterator creates a storage iterator over a single diff layer.
func newDiffStorageIterator(account common.Hash, seek common.Hash, states *stateSet, fn loadStorage) StorageIterator {
hashes := states.storageList(account)
index := sort.Search(len(hashes), func(i int) bool {
return bytes.Compare(seek[:], hashes[i][:]) <= 0
func newDiffStorageIterator(account common.Hash, seek common.Hash, storageList []common.Hash, fn loadStorage) StorageIterator {
index := sort.Search(len(storageList), func(i int) bool {
return bytes.Compare(seek[:], storageList[i][:]) <= 0
})
// Assemble and returned the already seeked iterator
return &diffStorageIterator{
account: account,
keys: hashes[index:],
keys: storageList[index:],
loadFn: fn,
}
}

View file

@ -45,6 +45,12 @@ type binaryIterator struct {
// accounts in a slow, but easily verifiable way. Note this function is used
// for initialization, use `newBinaryAccountIterator` as the API.
func (dl *diskLayer) initBinaryAccountIterator(seek common.Hash) *binaryIterator {
// The state set in the disk layer is mutable, hold the lock before obtaining
// the account list to prevent concurrent map iteration and write.
dl.lock.RLock()
accountList := dl.buffer.states.accountList()
dl.lock.RUnlock()
// Create two iterators for state buffer and the persistent state in disk
// respectively and combine them as a binary iterator.
l := &binaryIterator{
@ -54,7 +60,7 @@ func (dl *diskLayer) initBinaryAccountIterator(seek common.Hash) *binaryIterator
// The account key list for iteration is deterministic once the iterator
// is constructed, no matter the referenced disk layer is stale or not
// later.
a: newDiffAccountIterator(seek, dl.buffer.states, nil),
a: newDiffAccountIterator(seek, accountList, nil),
b: newDiskAccountIterator(dl.db.diskdb, seek),
}
l.aDone = !l.a.Next()
@ -68,6 +74,9 @@ func (dl *diskLayer) initBinaryAccountIterator(seek common.Hash) *binaryIterator
func (dl *diffLayer) initBinaryAccountIterator(seek common.Hash) *binaryIterator {
parent, ok := dl.parent.(*diffLayer)
if !ok {
// The state set in diff layer is immutable and will never be stale,
// so the read lock protection is unnecessary.
accountList := dl.states.stateSet.accountList()
l := &binaryIterator{
// The account loader function is unnecessary; the account key list
// produced by the supplied state set alone is sufficient for iteration.
@ -75,13 +84,16 @@ func (dl *diffLayer) initBinaryAccountIterator(seek common.Hash) *binaryIterator
// The account key list for iteration is deterministic once the iterator
// is constructed, no matter the referenced disk layer is stale or not
// later.
a: newDiffAccountIterator(seek, dl.states.stateSet, nil),
a: newDiffAccountIterator(seek, accountList, nil),
b: dl.parent.(*diskLayer).initBinaryAccountIterator(seek),
}
l.aDone = !l.a.Next()
l.bDone = !l.b.Next()
return l
}
// The state set in diff layer is immutable and will never be stale,
// so the read lock protection is unnecessary.
accountList := dl.states.stateSet.accountList()
l := &binaryIterator{
// The account loader function is unnecessary; the account key list
// produced by the supplied state set alone is sufficient for iteration.
@ -89,7 +101,7 @@ func (dl *diffLayer) initBinaryAccountIterator(seek common.Hash) *binaryIterator
// The account key list for iteration is deterministic once the iterator
// is constructed, no matter the referenced disk layer is stale or not
// later.
a: newDiffAccountIterator(seek, dl.states.stateSet, nil),
a: newDiffAccountIterator(seek, accountList, nil),
b: parent.initBinaryAccountIterator(seek),
}
l.aDone = !l.a.Next()
@ -101,6 +113,12 @@ func (dl *diffLayer) initBinaryAccountIterator(seek common.Hash) *binaryIterator
// storage slots in a slow, but easily verifiable way. Note this function is used
// for initialization, use `newBinaryStorageIterator` as the API.
func (dl *diskLayer) initBinaryStorageIterator(account common.Hash, seek common.Hash) *binaryIterator {
// The state set in the disk layer is mutable, hold the lock before obtaining
// the storage list to prevent concurrent map iteration and write.
dl.lock.RLock()
storageList := dl.buffer.states.storageList(account)
dl.lock.RUnlock()
// Create two iterators for state buffer and the persistent state in disk
// respectively and combine them as a binary iterator.
l := &binaryIterator{
@ -110,7 +128,7 @@ func (dl *diskLayer) initBinaryStorageIterator(account common.Hash, seek common.
// The storage key list for iteration is deterministic once the iterator
// is constructed, no matter the referenced disk layer is stale or not
// later.
a: newDiffStorageIterator(account, seek, dl.buffer.states, nil),
a: newDiffStorageIterator(account, seek, storageList, nil),
b: newDiskStorageIterator(dl.db.diskdb, account, seek),
}
l.aDone = !l.a.Next()
@ -124,6 +142,9 @@ func (dl *diskLayer) initBinaryStorageIterator(account common.Hash, seek common.
func (dl *diffLayer) initBinaryStorageIterator(account common.Hash, seek common.Hash) *binaryIterator {
parent, ok := dl.parent.(*diffLayer)
if !ok {
// The state set in diff layer is immutable and will never be stale,
// so the read lock protection is unnecessary.
storageList := dl.states.stateSet.storageList(account)
l := &binaryIterator{
// The storage loader function is unnecessary; the storage key list
// produced by the supplied state set alone is sufficient for iteration.
@ -131,13 +152,16 @@ func (dl *diffLayer) initBinaryStorageIterator(account common.Hash, seek common.
// The storage key list for iteration is deterministic once the iterator
// is constructed, no matter the referenced disk layer is stale or not
// later.
a: newDiffStorageIterator(account, seek, dl.states.stateSet, nil),
a: newDiffStorageIterator(account, seek, storageList, nil),
b: dl.parent.(*diskLayer).initBinaryStorageIterator(account, seek),
}
l.aDone = !l.a.Next()
l.bDone = !l.b.Next()
return l
}
// The state set in diff layer is immutable and will never be stale,
// so the read lock protection is unnecessary.
storageList := dl.states.stateSet.storageList(account)
l := &binaryIterator{
// The storage loader function is unnecessary; the storage key list
// produced by the supplied state set alone is sufficient for iteration.
@ -145,7 +169,7 @@ func (dl *diffLayer) initBinaryStorageIterator(account common.Hash, seek common.
// The storage key list for iteration is deterministic once the iterator
// is constructed, no matter the referenced disk layer is stale or not
// later.
a: newDiffStorageIterator(account, seek, dl.states.stateSet, nil),
a: newDiffStorageIterator(account, seek, storageList, nil),
b: parent.initBinaryStorageIterator(account, seek),
}
l.aDone = !l.a.Next()

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