mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 14:16:44 +00:00
Merge branch 'master' into upgrade_hasher_pool
This commit is contained in:
commit
35394489e5
105 changed files with 9110 additions and 1612 deletions
7
.mailmap
7
.mailmap
|
|
@ -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>
|
||||
|
|
|
|||
2
AUTHORS
2
AUTHORS
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
55
build/ci.go
55
build/ci.go
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = ð.StatusPacket{
|
||||
status = ð.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 {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const (
|
|||
// Unexported devp2p protocol lengths from p2p package.
|
||||
const (
|
||||
baseProtoLen = 16
|
||||
ethProtoLen = 17
|
||||
ethProtoLen = 18
|
||||
snapProtoLen = 8
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 := ð.GetBlockHeadersPacket{
|
||||
RequestId: 33,
|
||||
|
|
@ -160,18 +156,13 @@ 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)
|
||||
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 := ð.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 := ð.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 := ð.GetBlockHeadersPacket{
|
||||
GetBlockHeadersRequest: ð.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 := ð.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 := ð.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, ð.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, ð.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, ð.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.`)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ var PrecompiledContractsPrague = PrecompiledContracts{
|
|||
|
||||
var PrecompiledContractsBLS = PrecompiledContractsPrague
|
||||
|
||||
var PrecompiledContractsVerkle = PrecompiledContractsPrague
|
||||
var PrecompiledContractsVerkle = PrecompiledContractsBerlin
|
||||
|
||||
var (
|
||||
PrecompiledAddressesPrague []common.Address
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ func validate(jt JumpTable) JumpTable {
|
|||
}
|
||||
|
||||
func newVerkleInstructionSet() JumpTable {
|
||||
instructionSet := newCancunInstructionSet()
|
||||
instructionSet := newShanghaiInstructionSet()
|
||||
enable4762(&instructionSet)
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 := ð.Request{
|
||||
Peer: dlp.id,
|
||||
}
|
||||
resp := eth.ReceiptsRLPResponse(types.EncodeBlockReceiptLists(receipts))
|
||||
res := ð.Response{
|
||||
Req: req,
|
||||
Res: (*eth.ReceiptsResponse)(&receipts),
|
||||
Res: &resp,
|
||||
Meta: hashes,
|
||||
Time: 1,
|
||||
Done: make(chan error, 1), // Ignore the returned status
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
163
eth/handler.go
163
eth/handler.go
|
|
@ -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(ð.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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
18
eth/peer.go
18
eth/peer.go
|
|
@ -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 ðPeerInfo{
|
||||
Version: p.Version(),
|
||||
info := ðPeerInfo{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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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)},
|
||||
|
|
|
|||
462
eth/protocols/eth/receipt.go
Normal file
462
eth/protocols/eth/receipt.go
Normal 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
|
||||
}
|
||||
158
eth/protocols/eth/receipt_test.go
Normal file
158
eth/protocols/eth/receipt_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
}
|
||||
|
|
|
|||
144
eth/tracers/internal/tracetest/erc7562_tracer_test.go
Normal file
144
eth/tracers/internal/tracetest/erc7562_tracer_test.go
Normal 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))
|
||||
})
|
||||
}
|
||||
}
|
||||
110
eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_deployer.json
vendored
Normal file
110
eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_deployer.json
vendored
Normal file
File diff suppressed because one or more lines are too long
110
eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_paymaster.json
vendored
Normal file
110
eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_paymaster.json
vendored
Normal file
File diff suppressed because one or more lines are too long
246
eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_simple.json
vendored
Normal file
246
eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_simple.json
vendored
Normal file
File diff suppressed because one or more lines are too long
530
eth/tracers/native/erc7562.go
Normal file
530
eth/tracers/native/erc7562.go
Normal 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
|
||||
}
|
||||
159
eth/tracers/native/gen_callframewithopcodes_json.go
Normal file
159
eth/tracers/native/gen_callframewithopcodes_json.go
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
298
internal/download/download.go
Normal file
298
internal/download/download.go
Normal 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
|
||||
}
|
||||
1897
internal/era/eradl/checksums_mainnet.txt
Normal file
1897
internal/era/eradl/checksums_mainnet.txt
Normal file
File diff suppressed because it is too large
Load diff
183
internal/era/eradl/checksums_sepolia.txt
Normal file
183
internal/era/eradl/checksums_sepolia.txt
Normal 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
115
internal/era/eradl/eradl.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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
|
||||
];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
246
triedb/pathdb/context.go
Normal 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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
856
triedb/pathdb/generate.go
Normal 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"
|
||||
}
|
||||
766
triedb/pathdb/generate_test.go
Normal file
766
triedb/pathdb/generate_test.go
Normal 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()
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue