Compare commits

..

53 Commits

Author SHA1 Message Date
Felix Lange
ab48ba42f4 params: release go-ethereum v1.14.3 stable 2024-05-09 12:34:54 +02:00
Felix Lange
804afb8faa .travis.yml: restore PPA condition and bump timeouts (#29742) 2024-05-08 20:46:54 +02:00
Felix Lange
faff03c403 .travis.yml: enable PPA upload on push and fix apt-get command (#29741) 2024-05-08 20:28:05 +02:00
Felix Lange
1a79f8fe58 params: begin v1.14.3 release cycle 2024-05-08 16:31:14 +02:00
Felix Lange
35b2d07f4b params: release go-ethereum v1.14.2 stable 2024-05-08 16:26:01 +02:00
Felix Lange
eeb22089fd .travis.yml: fix package install on PPA builder 2024-05-08 14:34:58 +02:00
Felix Lange
14f4228472 params: begin v1.14.2 release cycle 2024-05-08 14:30:18 +02:00
Felix Lange
dd09f7e3fa params: release go-ethereum v1.14.1 stable 2024-05-08 14:28:40 +02:00
Felix Lange
6154f87c33 .travis.yml: fix apt-get options (#29734) 2024-05-08 11:33:07 +02:00
Felix Lange
dd4afb9fec .travis.yml: fix install of gcc-multilib (#29733) 2024-05-08 11:08:55 +02:00
rjl493456442
9ec50080eb core: use in-memory freezer for tests (#29720)
* core: simplify chain tests

* core, eth, cmd: use in-memory freezer for tests

* core: restore tests
2024-05-08 09:43:33 +03:00
Felix Lange
e96de6489c build: upgrade to go 1.22.3 (#29725) 2024-05-07 22:08:29 +02:00
Martin HS
71aa15c98f travis: use ubuntu noble (24.04) instead of bionic (18.04) (#29723) 2024-05-07 21:24:58 +02:00
nand2
d6e91e2e05 eth/gasestimator: include blobs in virtual balance computation (#29703)
Fixes #29702

Co-authored-by: Felix Lange <fjl@twurst.com>
2024-05-07 14:27:14 +02:00
Nathan
e4b8058d5a eth/gasprice: add query limit for FeeHistory to defend DDOS attack (#29644)
* eth/gasprice: add query limit for FeeHistory to defend DDOS attack

* fix return values after cherry-pick

---------

Co-authored-by: Eric <45141191+zlacfzy@users.noreply.github.com>
2024-05-07 10:25:15 +03:00
Maciej Kulawik
3e896c875a ethdb/pebble: fix pebble metrics registration (#29699)
ethdb/pebble: use GetOrRegister instead of NewRegistered when creating metrics
2024-05-06 14:42:22 +03:00
Guillaume Ballet
43cbcd78ea core, core/state: move TriesInMemory to state package (#29701) 2024-05-06 13:28:53 +02:00
Matthieu Vachon
a09a610384 core/tracing: add system call callback when performing ProcessBeaconBlockRoot (#29355)
Added a start/end system where tracer can be notified that processing of some Ethereum system calls is starting processing and also notifies it when the processing has completed.

Doing a start/end for system call will enable tracers to "route" incoming next tracing events to go to a separate bucket than other EVM calls. Those not interested by this fact can simply avoid registering the hooks.

The EVM call is going to be traced normally afterward between the signals provided by those 2 new hooks but outside of a transaction context OnTxStart/End. That something implementors of live tracers will need to be aware of (since only "trx tracers" are not concerned by ProcessBeaconRoot).

---------

Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
2024-05-06 13:21:55 +02:00
Kiarash Hajian
905e325cd8 p2p/discover/v5wire: add tests for invalid handshake and auth data size (#29708) 2024-05-06 13:17:19 +02:00
rjl493456442
86a1f0c394 core/rawdb: fix ancient root folder (#29697) 2024-05-02 13:26:07 +03:00
maskpp
2c67fab0d7 trie/pathdb: preallocate map capacity (#29690)
* preallocated capacity for map's certain usege of memory

* preallocated capacity for map's certain usege of memory
2024-05-02 12:35:45 +03:00
Nathan
fbf6238ae9 params: fix misleading comments (#29684) 2024-05-02 11:21:11 +03:00
Aaron Chen
bc609e852a core/vm: remove redundant error checks (#29692) 2024-05-02 11:18:59 +03:00
Péter Szilágyi
682ee820fa core/state: parallelise parts of state commit (#29681)
* core/state, internal/workerpool: parallelize parts of state commit

* core, internal: move workerpool into syncx

* core/state: use errgroups, commit accounts concurrently

* core: resurrect detailed commit timers to almost-accuracy
2024-05-02 11:18:27 +03:00
rjl493456442
9f96e07c1c core/rawdb, trie: improve db APIs for accessing trie nodes (#29362)
* core/rawdb, trie: improve db APIs for accessing trie nodes

* triedb/pathdb: fix
2024-04-30 16:25:35 +02:00
Bin
f8820f170c accounts, cmd/geth, core: close opened files (#29598)
* fix: open file used up but not closed

* feat: more same case

* feat: accept conversation
2024-04-30 15:47:21 +02:00
jwasinger
45baf21111 eth/downloader: purge pre-merge sync code (#29281)
This PR removes pre-merge sync logic from the downloader. Now-irrelevant tests are removed and others have been updated.
2024-04-30 15:46:53 +02:00
lightclient
2e8e35f2ad all: refactor so NewBlock, WithBody take types.Body (#29482)
* all: refactor so NewBlock(..) and WithBody(..) take a types.Body

* core: fixup comments, remove txs != receipts panic

* core/types: add empty withdrawls to body if len == 0
2024-04-30 14:55:08 +02:00
Martin HS
5e07054589 internal/ethapi: listen to ctx cancellation in access list (#29686) 2024-04-30 14:48:54 +02:00
Marius van der Wijden
bd6bc37eec core/vm: add subgroup checks for mul/mulexp for G1/G2 (#29637) 2024-04-30 14:35:48 +02:00
Dragan Milic
7c7e3a77fc eth/tracers/native: fix flatCallTracer Stop() bug (#29623)
Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
2024-04-30 14:33:22 +02:00
Aaron Chen
ea89f9adf0 core/vm: remove a redundant zero check in opAddmod (#29672) 2024-04-30 14:08:13 +02:00
Martin HS
242b24af9f trie/trienode: minor speedup in nodeset merging (#29683) 2024-04-30 19:51:04 +08:00
rjl493456442
f46c878441 core/rawdb: implement in-memory freezer (#29135) 2024-04-30 11:33:22 +02:00
felipe
c04b8e6d74 cmd/utils: require TTD and difficulty to be zero at genesis for dev mode (#29579) 2024-04-30 11:22:57 +02:00
Nathan
69f815f6f5 params: print time value instead of pointer in ConfigCompatError (#29514) 2024-04-30 11:22:02 +02:00
maskpp
fecc8a0f4a cmd/evm/internal/t8ntool, core: prealloc map sizes where possible (#29620)
set cap for map in a certain scenario
2024-04-30 11:19:59 +02:00
Aaron Chen
8c3fc56d7f p2p/simulations/adapters: use maps.Clone (#29626) 2024-04-29 19:44:41 +02:00
Roy Crihfield
4bdbaab471 params: clarify consensus engine config Strings (#29643)
Define these on a value receiever so that nil is shown differently.
2024-04-28 13:03:03 +02:00
Péter Szilágyi
4253030ef6 core/state: move metrics out of state objects (#29665) 2024-04-26 18:35:52 +03:00
Péter Szilágyi
8d42e115b1 core/state: revert pending storage updates if they revert to original (#29661) 2024-04-26 15:24:40 +03:00
Péter Szilágyi
ad4fb2c729 build: drop trusty from PPA builds, EOL and incompatible (#29651)
* build: drop trusty from PPA builds, EOL and incompatible

* build: add Ubuntu Noble PPA build target
2024-04-25 14:07:39 +03:00
Péter Szilágyi
634d037937 travis: revert the PPA fix hot-build, it works (#29649) 2024-04-25 12:27:36 +03:00
Péter Szilágyi
a0282fc94f travis: temporarilly enable PPA builds for testing (#29648) 2024-04-25 12:00:59 +03:00
Péter Szilágyi
1f628d842c build: build all the builders to build all the builders (#29647)
* build: build all the builders to build all the builders

* build: tweak the indexes a bit to make them consistent
2024-04-25 11:50:25 +03:00
Martin HS
243cde0f54 core/state: better randomized testing (postcheck) on journalling (#29627)
This PR fixes some flaws with the existing tests.

The randomized testing (TestSnapshotRandom) executes a series of steps which modify the state and create journal-events. Later on, we compare the forward-going-states against the backwards-unrolling-journal-states, and check that they are identical.

The "identical" check is performed using various accessors. It turned out that we failed to check some things: 
- the accesslist contents
- the transient storage contents
- the 'newContract' flag
- the dirty storage map

This change adds these new checks
2024-04-25 09:56:25 +02:00
Undefinedor
a13b92524d eth/protocols/eth,p2p/discover: remove unnecessary checks (#29590)
fix useless condition
2024-04-25 08:40:29 +02:00
yujinpark
2f6ff492ae internal/ethapi: typo (#29636) 2024-04-25 13:47:29 +08:00
Péter Szilágyi
4f4f9d88d3 core/state: storage journal entry should revert dirtyness too (#29641)
Currently our state journal tracks each storage update to a contract, having the ability to revert those changes to the previously set value.

For the very first modification however, it behaves a bit wonky. Reverting the update doesn't actually remove the dirty-ness of the slot, rather leaves it as "change this slot to it's original value". This can cause issues down the line with for example write witnesses needing to gather an unneeded proof.

This PR modifies the storageChange journal entry to not only track the previous value of a slot, but also whether there was any previous value at all set in the current execution context. In essence, the PR changes the semantic of storageChange so it does not simply track storage changes, rather it tracks dirty storage changes, an important distinction for being able to cleanly revert the journal item.
2024-04-24 17:45:24 +02:00
Aaron Chen
7362691479 trie, consensus/clique: use maps.Clone (#29616) 2024-04-24 14:27:58 +02:00
qcrao
ac21f9bfb5 trie: preallocate capacity for fields slice (#29614)
trie: Preallocate capacity for fields slice
2024-04-24 14:04:20 +02:00
Martin HS
0d4c38865e core/state: remove account reset operation v2 (#29520)
* core/state, tests: remove account reset operation

* core/state, core/vm: implement createcontract journal event

* core/state: make createcontract not emit dirtied account, unskip tests

* core/state: add createcontract to journal fuzzing

* core/state: fix journal

* core/state: address comments

* core/state: remove useless code

---------

Co-authored-by: Gary Rong <garyrong0905@gmail.com>
2024-04-24 12:59:06 +03:00
Péter Szilágyi
938734be3c params: begin 1.14.1 release cycle 2024-04-24 11:05:10 +03:00
117 changed files with 2655 additions and 2824 deletions

View File

@@ -15,7 +15,7 @@ jobs:
if: type = push if: type = push
os: linux os: linux
arch: amd64 arch: amd64
dist: bionic dist: noble
go: 1.22.x go: 1.22.x
env: env:
- docker - docker
@@ -32,7 +32,7 @@ jobs:
if: type = push if: type = push
os: linux os: linux
arch: arm64 arch: arm64
dist: bionic dist: noble
go: 1.22.x go: 1.22.x
env: env:
- docker - docker
@@ -49,21 +49,20 @@ jobs:
- stage: build - stage: build
if: type = push if: type = push
os: linux os: linux
dist: bionic dist: noble
sudo: required sudo: required
go: 1.22.x go: 1.22.x
env: env:
- azure-linux - azure-linux
git: git:
submodules: false # avoid cloning ethereum/tests submodules: false # avoid cloning ethereum/tests
addons:
apt:
packages:
- gcc-multilib
script: script:
# Build for the primary platforms that Trusty can manage # build amd64
- go run build/ci.go install -dlgo - go run build/ci.go install -dlgo
- go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
# build 386
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends install gcc-multilib
- go run build/ci.go install -dlgo -arch 386 - go run build/ci.go install -dlgo -arch 386
- go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
@@ -100,46 +99,30 @@ jobs:
- stage: build - stage: build
os: linux os: linux
arch: amd64 arch: amd64
dist: bionic dist: noble
go: 1.22.x go: 1.22.x
script: script:
- travis_wait 30 go run build/ci.go test $TEST_PACKAGES - travis_wait 45 go run build/ci.go test $TEST_PACKAGES
- stage: build
if: type = pull_request
os: linux
arch: arm64
dist: bionic
go: 1.21.x
script:
- travis_wait 30 go run build/ci.go test $TEST_PACKAGES
- stage: build - stage: build
os: linux os: linux
dist: bionic dist: noble
go: 1.21.x go: 1.21.x
script: script:
- travis_wait 30 go run build/ci.go test $TEST_PACKAGES - travis_wait 45 go run build/ci.go test $TEST_PACKAGES
# This builder does the Ubuntu PPA nightly uploads # This builder does the Ubuntu PPA nightly uploads
- stage: build - stage: build
if: type = cron || (type = push && tag ~= /^v[0-9]/) if: type = cron || (type = push && tag ~= /^v[0-9]/)
os: linux os: linux
dist: bionic dist: noble
go: 1.22.x go: 1.22.x
env: env:
- ubuntu-ppa - ubuntu-ppa
git: git:
submodules: false # avoid cloning ethereum/tests submodules: false # avoid cloning ethereum/tests
addons: before_install:
apt: - sudo -E apt-get -yq --no-install-suggests --no-install-recommends install devscripts debhelper dput fakeroot
packages:
- devscripts
- debhelper
- dput
- fakeroot
- python-bzrlib
- python-paramiko
script: script:
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts - echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>" - go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
@@ -148,7 +131,7 @@ jobs:
- stage: build - stage: build
if: type = cron if: type = cron
os: linux os: linux
dist: bionic dist: noble
go: 1.22.x go: 1.22.x
env: env:
- azure-purge - azure-purge
@@ -161,8 +144,7 @@ jobs:
- stage: build - stage: build
if: type = cron if: type = cron
os: linux os: linux
dist: bionic dist: noble
go: 1.22.x go: 1.22.x
script: script:
- travis_wait 30 go run build/ci.go test -race $TEST_PACKAGES - travis_wait 50 go run build/ci.go test -race $TEST_PACKAGES

View File

@@ -312,11 +312,10 @@ func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error {
// Lock removes the private key with the given address from memory. // Lock removes the private key with the given address from memory.
func (ks *KeyStore) Lock(addr common.Address) error { func (ks *KeyStore) Lock(addr common.Address) error {
ks.mu.Lock() ks.mu.Lock()
if unl, found := ks.unlocked[addr]; found { unl, found := ks.unlocked[addr]
ks.mu.Unlock() ks.mu.Unlock()
if found {
ks.expire(addr, unl, time.Duration(0)*time.Nanosecond) ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
} else {
ks.mu.Unlock()
} }
return nil return nil
} }

View File

@@ -95,6 +95,7 @@ func (hub *Hub) readPairings() error {
} }
return err return err
} }
defer pairingFile.Close()
pairingData, err := io.ReadAll(pairingFile) pairingData, err := io.ReadAll(pairingFile)
if err != nil { if err != nil {

View File

@@ -250,7 +250,7 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash,
BlobGasUsed: params.BlobGasUsed, BlobGasUsed: params.BlobGasUsed,
ParentBeaconRoot: beaconRoot, ParentBeaconRoot: beaconRoot,
} }
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals) block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: params.Withdrawals})
if block.Hash() != params.BlockHash { if block.Hash() != params.BlockHash {
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash()) return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash())
} }

View File

@@ -63,9 +63,7 @@ func convertPayload[T payloadType](payload T, parentRoot *zrntcommon.Root) (*typ
panic("unsupported block type") panic("unsupported block type")
} }
block := types.NewBlockWithHeader(&header) block := types.NewBlockWithHeader(&header).WithBody(types.Body{Transactions: transactions, Withdrawals: withdrawals})
block = block.WithBody(transactions, nil)
block = block.WithWithdrawals(withdrawals)
if hash := block.Hash(); hash != expectedHash { if hash := block.Hash(); hash != expectedHash {
return nil, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash) return nil, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
} }

View File

@@ -5,22 +5,48 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/ # https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/
ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz
# version:golang 1.22.2 # version:golang 1.22.3
# https://go.dev/dl/ # https://go.dev/dl/
374ea82b289ec738e968267cac59c7d5ff180f9492250254784b2044e90df5a9 go1.22.2.src.tar.gz 80648ef34f903193d72a59c0dff019f5f98ae0c9aa13ade0b0ecbff991a76f68 go1.22.3.src.tar.gz
33e7f63077b1c5bce4f1ecadd4d990cf229667c40bfb00686990c950911b7ab7 go1.22.2.darwin-amd64.tar.gz adc9f5fee89cd53d907eb542d3b269d9d8a08a66bf1ab42175450ffbb58733fb go1.22.3.aix-ppc64.tar.gz
660298be38648723e783ba0398e90431de1cb288c637880cdb124f39bd977f0d go1.22.2.darwin-arm64.tar.gz 610e48c1df4d2f852de8bc2e7fd2dc1521aac216f0c0026625db12f67f192024 go1.22.3.darwin-amd64.tar.gz
efc7162b0cad2f918ac566a923d4701feb29dc9c0ab625157d49b1cbcbba39da go1.22.2.freebsd-386.tar.gz 02abeab3f4b8981232237ebd88f0a9bad933bc9621791cd7720a9ca29eacbe9d go1.22.3.darwin-arm64.tar.gz
d753428296e6709527e291fd204700a587ffef2c0a472b21aebea11618245929 go1.22.2.freebsd-amd64.tar.gz a5b3d54905f17af2ceaf7fcfe92edee67a5bd4eccd962dd89df719ace3e0894d go1.22.3.dragonfly-amd64.tar.gz
586d9eb7fe0489ab297ad80dd06414997df487c5cf536c490ffeaa8d8f1807a7 go1.22.2.linux-386.tar.gz b9989ca87695ae93bacde6f3aa7b13cde5f3825515eb9ed9bbef014273739889 go1.22.3.freebsd-386.tar.gz
5901c52b7a78002aeff14a21f93e0f064f74ce1360fce51c6ee68cd471216a17 go1.22.2.linux-amd64.tar.gz 7483961fae29d7d768afd5c9c0f229354ca3263ab7119c20bc182761f87cbc74 go1.22.3.freebsd-amd64.tar.gz
36e720b2d564980c162a48c7e97da2e407dfcc4239e1e58d98082dfa2486a0c1 go1.22.2.linux-arm64.tar.gz edf1f0b8ecf68b14faeedb4f5d868a58c4777a0282bd85e5115c39c010cd0130 go1.22.3.freebsd-arm.tar.gz
9243dfafde06e1efe24d59df6701818e6786b4adfdf1191098050d6d023c5369 go1.22.2.linux-armv6l.tar.gz 572eb70e5e835fbff7d53ebf473f611d7eb458c428f8dbd98a49196883c3309e go1.22.3.freebsd-arm64.tar.gz
251a8886c5113be6490bdbb955ddee98763b49c9b1bf4c8364c02d3b482dab00 go1.22.2.linux-ppc64le.tar.gz ef94eb2b74402e436dce970584222c4e454eb3093908591149bd2ded6862b8af go1.22.3.freebsd-riscv64.tar.gz
2b39019481c28c560d65e9811a478ae10e3ef765e0f59af362031d386a71bfef go1.22.2.linux-s390x.tar.gz 3c3f498c68334cbd11f72aadfb6bcb507eb8436cebc50f437a0523cd4c5e03d1 go1.22.3.illumos-amd64.tar.gz
651753c06df037020ef4d162c5b273452e9ba976ed17ae39e66ef7ee89d8147e go1.22.2.windows-386.zip fefba30bb0d3dd1909823ee38c9f1930c3dc5337a2ac4701c2277a329a386b57 go1.22.3.linux-386.tar.gz
8e581cf330f49d3266e936521a2d8263679ef7e2fc2cbbceb85659122d883596 go1.22.2.windows-amd64.zip 8920ea521bad8f6b7bc377b4824982e011c19af27df88a815e3586ea895f1b36 go1.22.3.linux-amd64.tar.gz
ddfca5beb9a0c62254266c3090c2555d899bf3e7aa26243e7de3621108f06875 go1.22.2.windows-arm64.zip 6c33e52a5b26e7aa021b94475587fce80043a727a54ceb0eee2f9fc160646434 go1.22.3.linux-arm64.tar.gz
f2bacad20cd2b96f23a86d4826525d42b229fd431cc6d0dec61ff3bc448ef46e go1.22.3.linux-armv6l.tar.gz
41e9328340544893482b2928ae18a9a88ba18b2fdd29ac77f4d33cf1815bbdc2 go1.22.3.linux-loong64.tar.gz
cf4d5faff52e642492729eaf396968f43af179518be769075b90bc1bf650abf6 go1.22.3.linux-mips.tar.gz
3bd009fe2e3d2bfd52433a11cb210d1dfa50b11b4c347a293951efd9e36de945 go1.22.3.linux-mips64.tar.gz
5913b82a042188ef698f7f2dfd0cd0c71f0508a4739de9e41fceff3f4dc769b4 go1.22.3.linux-mips64le.tar.gz
441afebca555be5313867b4577f237c7b5c0fff4386e22e47875b9f805abbec5 go1.22.3.linux-mipsle.tar.gz
f3b53190a76f4a35283501ba6d94cbb72093be0c62ff735c6f9e586a1c983381 go1.22.3.linux-ppc64.tar.gz
04b7b05283de30dd2da20bf3114b2e22cc727938aed3148babaf35cc951051ac go1.22.3.linux-ppc64le.tar.gz
d4992d4a85696e3f1de06cefbfc2fd840c9c6695d77a0f35cfdc4e28b2121c20 go1.22.3.linux-riscv64.tar.gz
2aba796417a69be5f3ed489076bac79c1c02b36e29422712f9f3bf51da9cf2d4 go1.22.3.linux-s390x.tar.gz
d6e6113542dd9f23db899e177fe23772bac114a5ea5e8ee436b9da68628335a8 go1.22.3.netbsd-386.tar.gz
c33cee3075bd18ceefddd75bafa8efb51fbdc17b5ee74275122e7a927a237a4c go1.22.3.netbsd-amd64.tar.gz
1ab251df3c85f3b391a09565ca52fb6e1306527d72852d553e9ab74eabb4ecf8 go1.22.3.netbsd-arm.tar.gz
1d194fe53f5d82f9a612f848950d8af8cab7cb40ccc03f10c4eb1c9808ff1a0c go1.22.3.netbsd-arm64.tar.gz
91d6601727f08506e938640885d3ded784925045e3a4444fd9b4b936efe1b1e0 go1.22.3.openbsd-386.tar.gz
09d0c91ae35a4eea92615426992062ca236cc2f66444fb0b0a24cd3b13bd5297 go1.22.3.openbsd-amd64.tar.gz
338da30cc2c97b9458e0b4caa2509f67bba55d3de16fb7d31775baca82d2e3dc go1.22.3.openbsd-arm.tar.gz
53eadfabd2b7dd09a64941421afee2a2888e2a4f94f353b27919b1dad1171a21 go1.22.3.openbsd-arm64.tar.gz
8a1a2842ae8dcf2374bb05dff58074b368bb698dc9c211c794c1ff119cd9fdc7 go1.22.3.plan9-386.tar.gz
f9816d3dd9e730cad55085ea08c1f0c925720728f9c945fff59cd24d2ac2db7b go1.22.3.plan9-amd64.tar.gz
f4d3d7b17c9e1b1635fcb287b5b5ab5b60acc9db3ba6a27f2b2f5d6537a2ef95 go1.22.3.plan9-arm.tar.gz
46b7999ee94d91b21ad6940b5a3131ff6fe53ef97be9a34e582e2a3ad7263e95 go1.22.3.solaris-amd64.tar.gz
f60f63b8a0885e0d924f39fd284aee5438fe87d8c3d8545a312adf43e0d9edac go1.22.3.windows-386.zip
cab2af6951a6e2115824263f6df13ff069c47270f5788714fa1d776f7f60cb39 go1.22.3.windows-amd64.zip
40b37f4b068fc759f3a0dd61176a0f7570a4ba48bed8561c31d3967a3583981a go1.22.3.windows-arm.zip
59b76ee22b9b1c3afbf7f50e3cb4edb954d6c0d25e5e029ab5483a6804d61e71 go1.22.3.windows-arm64.zip
# version:golangci 1.55.2 # version:golangci 1.55.2
# https://github.com/golangci/golangci-lint/releases/ # https://github.com/golangci/golangci-lint/releases/
@@ -56,10 +82,12 @@ a5e68ae73d38748b5269fad36ac7575e3c162a5dc63ef58abdea03cc5da4522a golangci-lint-
# This is the builder on PPA that will build Go itself (inception-y), don't modify! # This is the builder on PPA that will build Go itself (inception-y), don't modify!
# #
# This version is fine to be old and full of security holes, we just use it # This version is fine to be old and full of security holes, we just use it
# to build the latest Go. Don't change it. If it ever becomes insufficient, # to build the latest Go. Don't change it.
# we need to switch over to a recursive builder to jump across supported
# versions.
# #
# version:ppa-builder 1.19.6 # version:ppa-builder-1 1.19.6
# https://go.dev/dl/ # https://go.dev/dl/
d7f0013f82e6d7f862cc6cb5c8cdb48eef5f2e239b35baa97e2f1a7466043767 go1.19.6.src.tar.gz d7f0013f82e6d7f862cc6cb5c8cdb48eef5f2e239b35baa97e2f1a7466043767 go1.19.6.src.tar.gz
# version:ppa-builder-2 1.21.9
# https://go.dev/dl/
58f0c5ced45a0012bce2ff7a9df03e128abcc8818ebabe5027bb92bafe20e421 go1.21.9.src.tar.gz

View File

@@ -117,23 +117,15 @@ var (
debEthereum, debEthereum,
} }
// Distros for which packages are created. // Distros for which packages are created
// Note: vivid is unsupported because there is no golang-1.6 package for it. debDistros = []string{
// Note: the following Ubuntu releases have been officially deprecated on Launchpad: "xenial", // 16.04, EOL: 04/2026
// wily, yakkety, zesty, artful, cosmic, disco, eoan, groovy, hirsuite, impish, "bionic", // 18.04, EOL: 04/2028
// kinetic, lunar "focal", // 20.04, EOL: 04/2030
debDistroGoBoots = map[string]string{ "jammy", // 22.04, EOL: 04/2032
"trusty": "golang-1.11", // 14.04, EOL: 04/2024 "noble", // 24.04, EOL: 04/2034
"xenial": "golang-go", // 16.04, EOL: 04/2026
"bionic": "golang-go", // 18.04, EOL: 04/2028
"focal": "golang-go", // 20.04, EOL: 04/2030
"jammy": "golang-go", // 22.04, EOL: 04/2032
"mantic": "golang-go", // 23.10, EOL: 07/2024
}
debGoBootPaths = map[string]string{ "mantic", // 23.10, EOL: 07/2024
"golang-1.11": "/usr/lib/go-1.11",
"golang-go": "/usr/lib/go",
} }
// This is where the tests should be unpacked. // This is where the tests should be unpacked.
@@ -694,8 +686,8 @@ func doDebianSource(cmdline []string) {
} }
// Download and verify the Go source packages. // Download and verify the Go source packages.
var ( var (
gobootbundle = downloadGoBootstrapSources(*cachedir) gobootbundles = downloadGoBootstrapSources(*cachedir)
gobundle = downloadGoSources(*cachedir) gobundle = downloadGoSources(*cachedir)
) )
// Download all the dependencies needed to build the sources and run the ci script // Download all the dependencies needed to build the sources and run the ci script
srcdepfetch := tc.Go("mod", "download") srcdepfetch := tc.Go("mod", "download")
@@ -708,17 +700,19 @@ func doDebianSource(cmdline []string) {
// Create Debian packages and upload them. // Create Debian packages and upload them.
for _, pkg := range debPackages { for _, pkg := range debPackages {
for distro, goboot := range debDistroGoBoots { for _, distro := range debDistros {
// Prepare the debian package with the go-ethereum sources. // Prepare the debian package with the go-ethereum sources.
meta := newDebMetadata(distro, goboot, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables) meta := newDebMetadata(distro, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables)
pkgdir := stageDebianSource(*workdir, meta) pkgdir := stageDebianSource(*workdir, meta)
// Add bootstrapper Go source code // Add bootstrapper Go source code
if err := build.ExtractArchive(gobootbundle, pkgdir); err != nil { for i, gobootbundle := range gobootbundles {
log.Fatalf("Failed to extract bootstrapper Go sources: %v", err) if err := build.ExtractArchive(gobootbundle, pkgdir); err != nil {
} log.Fatalf("Failed to extract bootstrapper Go sources: %v", err)
if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".goboot")); err != nil { }
log.Fatalf("Failed to rename bootstrapper Go source folder: %v", err) if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, fmt.Sprintf(".goboot-%d", i+1))); err != nil {
log.Fatalf("Failed to rename bootstrapper Go source folder: %v", err)
}
} }
// Add builder Go source code // Add builder Go source code
if err := build.ExtractArchive(gobundle, pkgdir); err != nil { if err := build.ExtractArchive(gobundle, pkgdir); err != nil {
@@ -754,21 +748,26 @@ func doDebianSource(cmdline []string) {
} }
} }
// downloadGoBootstrapSources downloads the Go source tarball that will be used // downloadGoBootstrapSources downloads the Go source tarball(s) that will be used
// to bootstrap the builder Go. // to bootstrap the builder Go.
func downloadGoBootstrapSources(cachedir string) string { func downloadGoBootstrapSources(cachedir string) []string {
csdb := build.MustLoadChecksums("build/checksums.txt") csdb := build.MustLoadChecksums("build/checksums.txt")
gobootVersion, err := build.Version(csdb, "ppa-builder")
if err != nil { var bundles []string
log.Fatal(err) for _, booter := range []string{"ppa-builder-1", "ppa-builder-2"} {
gobootVersion, err := build.Version(csdb, 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 {
log.Fatal(err)
}
bundles = append(bundles, dst)
} }
file := fmt.Sprintf("go%s.src.tar.gz", gobootVersion) return bundles
url := "https://dl.google.com/go/" + file
dst := filepath.Join(cachedir, file)
if err := csdb.DownloadFile(url, dst); err != nil {
log.Fatal(err)
}
return dst
} }
// downloadGoSources downloads the Go source tarball. // downloadGoSources downloads the Go source tarball.
@@ -846,10 +845,7 @@ type debPackage struct {
} }
type debMetadata struct { type debMetadata struct {
Env build.Environment Env build.Environment
GoBootPackage string
GoBootPath string
PackageName string PackageName string
// go-ethereum version being built. Note that this // go-ethereum version being built. Note that this
@@ -877,21 +873,19 @@ func (d debExecutable) Package() string {
return d.BinaryName return d.BinaryName
} }
func newDebMetadata(distro, goboot, author string, env build.Environment, t time.Time, name string, version string, exes []debExecutable) debMetadata { func newDebMetadata(distro, author string, env build.Environment, t time.Time, name string, version string, exes []debExecutable) debMetadata {
if author == "" { if author == "" {
// No signing key, use default author. // No signing key, use default author.
author = "Ethereum Builds <fjl@ethereum.org>" author = "Ethereum Builds <fjl@ethereum.org>"
} }
return debMetadata{ return debMetadata{
GoBootPackage: goboot, PackageName: name,
GoBootPath: debGoBootPaths[goboot], Env: env,
PackageName: name, Author: author,
Env: env, Distro: distro,
Author: author, Version: version,
Distro: distro, Time: t.Format(time.RFC1123Z),
Version: version, Executables: exes,
Time: t.Format(time.RFC1123Z),
Executables: exes,
} }
} }

View File

@@ -2,7 +2,7 @@ Source: {{.Name}}
Section: science Section: science
Priority: extra Priority: extra
Maintainer: {{.Author}} Maintainer: {{.Author}}
Build-Depends: debhelper (>= 8.0.0), {{.GoBootPackage}} Build-Depends: debhelper (>= 8.0.0), golang-go
Standards-Version: 3.9.5 Standards-Version: 3.9.5
Homepage: https://ethereum.org Homepage: https://ethereum.org
Vcs-Git: https://github.com/ethereum/go-ethereum.git Vcs-Git: https://github.com/ethereum/go-ethereum.git

View File

@@ -7,7 +7,7 @@
# Launchpad rejects Go's access to $HOME, use custom folders # Launchpad rejects Go's access to $HOME, use custom folders
export GOCACHE=/tmp/go-build export GOCACHE=/tmp/go-build
export GOPATH=/tmp/gopath export GOPATH=/tmp/gopath
export GOROOT_BOOTSTRAP={{.GoBootPath}} export GOROOT_BOOTSTRAP=/usr/lib/go
override_dh_auto_clean: override_dh_auto_clean:
# Don't try to be smart Launchpad, we know our build rules better than you # Don't try to be smart Launchpad, we know our build rules better than you
@@ -19,8 +19,9 @@ override_dh_auto_build:
# #
# We're also shipping the bootstrapper as of Go 1.20 as it had minimum version # We're also shipping the bootstrapper as of Go 1.20 as it had minimum version
# requirements opposed to older versions of Go. # requirements opposed to older versions of Go.
(mv .goboot ../ && cd ../.goboot/src && ./make.bash) (mv .goboot-1 ../ && cd ../.goboot-1/src && ./make.bash)
(mv .go ../ && cd ../.go/src && GOROOT_BOOTSTRAP=`pwd`/../../.goboot ./make.bash) (mv .goboot-2 ../ && cd ../.goboot-2/src && GOROOT_BOOTSTRAP=`pwd`/../../.goboot-1 ./make.bash)
(mv .go ../ && cd ../.go/src && GOROOT_BOOTSTRAP=`pwd`/../../.goboot-2 ./make.bash)
# We can't download external go modules within Launchpad, so we're shipping the # We can't download external go modules within Launchpad, so we're shipping the
# entire dependency source cache with go-ethereum. # entire dependency source cache with go-ethereum.

View File

@@ -32,7 +32,6 @@ import (
"github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/internal/utesting"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"golang.org/x/crypto/sha3"
) )
func (c *Conn) snapRequest(code uint64, msg any) (any, error) { func (c *Conn) snapRequest(code uint64, msg any) (any, error) {
@@ -905,7 +904,7 @@ func (s *Suite) snapGetByteCodes(t *utesting.T, tc *byteCodesTest) error {
// that the serving node is missing // that the serving node is missing
var ( var (
bytecodes = res.Codes bytecodes = res.Codes
hasher = sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher = crypto.NewKeccakState()
hash = make([]byte, 32) hash = make([]byte, 32)
codes = make([][]byte, len(req.Hashes)) codes = make([][]byte, len(req.Hashes))
) )
@@ -964,7 +963,7 @@ func (s *Suite) snapGetTrieNodes(t *utesting.T, tc *trieNodesTest) error {
// Cross reference the requested trienodes with the response to find gaps // Cross reference the requested trienodes with the response to find gaps
// that the serving node is missing // that the serving node is missing
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher := crypto.NewKeccakState()
hash := make([]byte, 32) hash := make([]byte, 32)
trienodes := res.Nodes trienodes := res.Nodes
if got, want := len(trienodes), len(tc.expHashes); got != want { if got, want := len(trienodes), len(tc.expHashes); got != want {

View File

@@ -160,7 +160,7 @@ func (i *bbInput) ToBlock() *types.Block {
if i.Header.Difficulty != nil { if i.Header.Difficulty != nil {
header.Difficulty = i.Header.Difficulty header.Difficulty = i.Header.Difficulty
} }
return types.NewBlockWithHeader(header).WithBody(i.Txs, i.Ommers).WithWithdrawals(i.Withdrawals) return types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: i.Txs, Uncles: i.Ommers, Withdrawals: i.Withdrawals})
} }
// SealBlock seals the given block using the configured engine. // SealBlock seals the given block using the configured engine.

View File

@@ -296,7 +296,7 @@ func (g Alloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
balance, _ := new(big.Int).SetString(dumpAccount.Balance, 0) balance, _ := new(big.Int).SetString(dumpAccount.Balance, 0)
var storage map[common.Hash]common.Hash var storage map[common.Hash]common.Hash
if dumpAccount.Storage != nil { if dumpAccount.Storage != nil {
storage = make(map[common.Hash]common.Hash) storage = make(map[common.Hash]common.Hash, len(dumpAccount.Storage))
for k, v := range dumpAccount.Storage { for k, v := range dumpAccount.Storage {
storage[k] = common.HexToHash(v) storage[k] = common.HexToHash(v)
} }

View File

@@ -246,11 +246,17 @@ func removeDB(ctx *cli.Context) error {
ancientDir = config.Node.ResolvePath(ancientDir) ancientDir = config.Node.ResolvePath(ancientDir)
} }
// Delete state data // Delete state data
statePaths := []string{rootDir, filepath.Join(ancientDir, rawdb.StateFreezerName)} statePaths := []string{
rootDir,
filepath.Join(ancientDir, rawdb.StateFreezerName),
}
confirmAndRemoveDB(statePaths, "state data", ctx, removeStateDataFlag.Name) confirmAndRemoveDB(statePaths, "state data", ctx, removeStateDataFlag.Name)
// Delete ancient chain // Delete ancient chain
chainPaths := []string{filepath.Join(ancientDir, rawdb.ChainFreezerName)} chainPaths := []string{filepath.Join(
ancientDir,
rawdb.ChainFreezerName,
)}
confirmAndRemoveDB(chainPaths, "ancient chain", ctx, removeChainDataFlag.Name) confirmAndRemoveDB(chainPaths, "ancient chain", ctx, removeChainDataFlag.Name)
return nil return nil
} }

View File

@@ -73,6 +73,7 @@ func testConsoleLogging(t *testing.T, format string, tStart, tEnd int) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer readFile.Close()
wantLines := split(readFile) wantLines := split(readFile)
haveLines := split(bytes.NewBuffer(haveB)) haveLines := split(bytes.NewBuffer(haveB))
for i, want := range wantLines { for i, want := range wantLines {
@@ -109,6 +110,7 @@ func TestJsonLogging(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer readFile.Close()
wantLines := split(readFile) wantLines := split(readFile)
haveLines := split(bytes.NewBuffer(haveB)) haveLines := split(bytes.NewBuffer(haveB))
for i, wantLine := range wantLines { for i, wantLine := range wantLines {

View File

@@ -1872,13 +1872,15 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
Fatalf("Could not read genesis from database: %v", err) Fatalf("Could not read genesis from database: %v", err)
} }
if !genesis.Config.TerminalTotalDifficultyPassed { if !genesis.Config.TerminalTotalDifficultyPassed {
Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficultyPassed must be true in developer mode") Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficultyPassed must be true")
} }
if genesis.Config.TerminalTotalDifficulty == nil { if genesis.Config.TerminalTotalDifficulty == nil {
Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficulty must be specified.") Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficulty must be specified")
} else if genesis.Config.TerminalTotalDifficulty.Cmp(big.NewInt(0)) != 0 {
Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficulty must be 0")
} }
if genesis.Difficulty.Cmp(genesis.Config.TerminalTotalDifficulty) != 1 { if genesis.Difficulty.Cmp(big.NewInt(0)) != 0 {
Fatalf("Bad developer-mode genesis configuration: genesis block difficulty must be > terminalTotalDifficulty") Fatalf("Bad developer-mode genesis configuration: difficulty must be 0")
} }
} }
chaindb.Close() chaindb.Close()

View File

@@ -162,8 +162,7 @@ func TestHistoryImportAndExport(t *testing.T) {
} }
// Now import Era. // Now import Era.
freezer := t.TempDir() db2, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
db2, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), freezer, "", false)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -388,7 +388,7 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
header.Root = state.IntermediateRoot(true) header.Root = state.IntermediateRoot(true)
// Assemble and return the final block. // Assemble and return the final block.
return types.NewBlockWithWithdrawals(header, body.Transactions, body.Uncles, receipts, body.Withdrawals, trie.NewStackTrie(nil)), nil return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)), nil
} }
// Seal generates a new sealing request for the given input block and pushes // Seal generates a new sealing request for the given input block and pushes

View File

@@ -597,7 +597,7 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
// Assemble and return the final block for sealing. // Assemble and return the final block for sealing.
return types.NewBlock(header, body.Transactions, nil, receipts, trie.NewStackTrie(nil)), nil return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil
} }
// Authorize injects a private key into the consensus engine to mint new blocks // Authorize injects a private key into the consensus engine to mint new blocks

View File

@@ -19,6 +19,7 @@ package clique
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"maps"
"slices" "slices"
"time" "time"
@@ -108,28 +109,16 @@ func (s *Snapshot) store(db ethdb.Database) error {
// copy creates a deep copy of the snapshot, though not the individual votes. // copy creates a deep copy of the snapshot, though not the individual votes.
func (s *Snapshot) copy() *Snapshot { func (s *Snapshot) copy() *Snapshot {
cpy := &Snapshot{ return &Snapshot{
config: s.config, config: s.config,
sigcache: s.sigcache, sigcache: s.sigcache,
Number: s.Number, Number: s.Number,
Hash: s.Hash, Hash: s.Hash,
Signers: make(map[common.Address]struct{}), Signers: maps.Clone(s.Signers),
Recents: make(map[uint64]common.Address), Recents: maps.Clone(s.Recents),
Votes: make([]*Vote, len(s.Votes)), Votes: slices.Clone(s.Votes),
Tally: make(map[common.Address]Tally), Tally: maps.Clone(s.Tally),
} }
for signer := range s.Signers {
cpy.Signers[signer] = struct{}{}
}
for block, signer := range s.Recents {
cpy.Recents[block] = signer
}
for address, tally := range s.Tally {
cpy.Tally[address] = tally
}
copy(cpy.Votes, s.Votes)
return cpy
} }
// validVote returns whether it makes sense to cast the specified vote in the // validVote returns whether it makes sense to cast the specified vote in the

View File

@@ -520,7 +520,7 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
// Header seems complete, assemble into a block and return // Header seems complete, assemble into a block and return
return types.NewBlock(header, body.Transactions, body.Uncles, receipts, trie.NewStackTrie(nil)), nil return types.NewBlock(header, &types.Body{Transactions: body.Transactions, Uncles: body.Uncles}, receipts, trie.NewStackTrie(nil)), nil
} }
// SealHash returns the hash of a block prior to it being sealed. // SealHash returns the hash of a block prior to it being sealed.

View File

@@ -154,12 +154,10 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
preHeaders := make([]*types.Header, len(preBlocks)) preHeaders := make([]*types.Header, len(preBlocks))
for i, block := range preBlocks { for i, block := range preBlocks {
preHeaders[i] = block.Header() preHeaders[i] = block.Header()
t.Logf("Pre-merge header: %d", block.NumberU64())
} }
postHeaders := make([]*types.Header, len(postBlocks)) postHeaders := make([]*types.Header, len(postBlocks))
for i, block := range postBlocks { for i, block := range postBlocks {
postHeaders[i] = block.Header() postHeaders[i] = block.Header()
t.Logf("Post-merge header: %d", block.NumberU64())
} }
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil)

View File

@@ -68,7 +68,6 @@ var (
accountCommitTimer = metrics.NewRegisteredResettingTimer("chain/account/commits", nil) accountCommitTimer = metrics.NewRegisteredResettingTimer("chain/account/commits", nil)
storageReadTimer = metrics.NewRegisteredResettingTimer("chain/storage/reads", nil) storageReadTimer = metrics.NewRegisteredResettingTimer("chain/storage/reads", nil)
storageHashTimer = metrics.NewRegisteredResettingTimer("chain/storage/hashes", nil)
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil) storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil) storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
@@ -101,7 +100,6 @@ const (
blockCacheLimit = 256 blockCacheLimit = 256
receiptsCacheLimit = 32 receiptsCacheLimit = 32
txLookupCacheLimit = 1024 txLookupCacheLimit = 1024
TriesInMemory = 128
// BlockChainVersion ensures that an incompatible database forces a resync from scratch. // BlockChainVersion ensures that an incompatible database forces a resync from scratch.
// //
@@ -1129,7 +1127,7 @@ func (bc *BlockChain) Stop() {
if !bc.cacheConfig.TrieDirtyDisabled { if !bc.cacheConfig.TrieDirtyDisabled {
triedb := bc.triedb triedb := bc.triedb
for _, offset := range []uint64{0, 1, TriesInMemory - 1} { for _, offset := range []uint64{0, 1, state.TriesInMemory - 1} {
if number := bc.CurrentBlock().Number.Uint64(); number > offset { if number := bc.CurrentBlock().Number.Uint64(); number > offset {
recent := bc.GetBlockByNumber(number - offset) recent := bc.GetBlockByNumber(number - offset)
@@ -1310,7 +1308,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
// Delete block data from the main database. // Delete block data from the main database.
var ( var (
batch = bc.db.NewBatch() batch = bc.db.NewBatch()
canonHashes = make(map[common.Hash]struct{}) canonHashes = make(map[common.Hash]struct{}, len(blockChain))
) )
for _, block := range blockChain { for _, block := range blockChain {
canonHashes[block.Hash()] = struct{}{} canonHashes[block.Hash()] = struct{}{}
@@ -1453,7 +1451,7 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error {
// writeBlockWithState writes block, metadata and corresponding state data to the // writeBlockWithState writes block, metadata and corresponding state data to the
// database. // database.
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) error { func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, statedb *state.StateDB) error {
// Calculate the total difficulty of the block // Calculate the total difficulty of the block
ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1) ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
if ptd == nil { if ptd == nil {
@@ -1470,12 +1468,12 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
rawdb.WriteTd(blockBatch, block.Hash(), block.NumberU64(), externTd) rawdb.WriteTd(blockBatch, block.Hash(), block.NumberU64(), externTd)
rawdb.WriteBlock(blockBatch, block) rawdb.WriteBlock(blockBatch, block)
rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts) rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
rawdb.WritePreimages(blockBatch, state.Preimages()) rawdb.WritePreimages(blockBatch, statedb.Preimages())
if err := blockBatch.Write(); err != nil { if err := blockBatch.Write(); err != nil {
log.Crit("Failed to write block into disk", "err", err) log.Crit("Failed to write block into disk", "err", err)
} }
// Commit all cached state changes into underlying memory database. // Commit all cached state changes into underlying memory database.
root, err := state.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number())) root, err := statedb.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()))
if err != nil { if err != nil {
return err return err
} }
@@ -1494,7 +1492,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
// Flush limits are not considered for the first TriesInMemory blocks. // Flush limits are not considered for the first TriesInMemory blocks.
current := block.NumberU64() current := block.NumberU64()
if current <= TriesInMemory { if current <= state.TriesInMemory {
return nil return nil
} }
// If we exceeded our memory allowance, flush matured singleton nodes to disk // If we exceeded our memory allowance, flush matured singleton nodes to disk
@@ -1506,7 +1504,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
bc.triedb.Cap(limit - ethdb.IdealBatchSize) bc.triedb.Cap(limit - ethdb.IdealBatchSize)
} }
// Find the next state trie we need to commit // Find the next state trie we need to commit
chosen := current - TriesInMemory chosen := current - state.TriesInMemory
flushInterval := time.Duration(bc.flushInterval.Load()) flushInterval := time.Duration(bc.flushInterval.Load())
// If we exceeded time allowance, flush an entire trie to disk // If we exceeded time allowance, flush an entire trie to disk
if bc.gcproc > flushInterval { if bc.gcproc > flushInterval {
@@ -1518,8 +1516,8 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
} else { } else {
// If we're exceeding limits but haven't reached a large enough memory gap, // If we're exceeding limits but haven't reached a large enough memory gap,
// warn the user that the system is becoming unstable. // warn the user that the system is becoming unstable.
if chosen < bc.lastWrite+TriesInMemory && bc.gcproc >= 2*flushInterval { if chosen < bc.lastWrite+state.TriesInMemory && bc.gcproc >= 2*flushInterval {
log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", flushInterval, "optimum", float64(chosen-bc.lastWrite)/TriesInMemory) log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", flushInterval, "optimum", float64(chosen-bc.lastWrite)/state.TriesInMemory)
} }
// Flush an entire trie and restart the counters // Flush an entire trie and restart the counters
bc.triedb.Commit(header.Root, true) bc.triedb.Commit(header.Root, true)
@@ -1937,8 +1935,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation) accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation)
storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation) storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation)
accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation) accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation)
storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation) triehash := statedb.AccountHashes // The time spent on tries hashing
triehash := statedb.AccountHashes + statedb.StorageHashes // The time spent on tries hashing
trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update
trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read
trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read
@@ -1965,7 +1962,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them
triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits) blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits)
blockInsertTimer.UpdateSince(start) blockInsertTimer.UpdateSince(start)
return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil

View File

@@ -785,7 +785,7 @@ func testFastVsFullChains(t *testing.T, scheme string) {
t.Fatalf("failed to insert receipt %d: %v", n, err) t.Fatalf("failed to insert receipt %d: %v", n, err)
} }
// Freezer style fast import the chain. // Freezer style fast import the chain.
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
if err != nil { if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err) t.Fatalf("failed to create temp freezer db: %v", err)
} }
@@ -875,12 +875,12 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
} }
) )
height := uint64(1024) height := uint64(64)
_, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil) _, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil)
// makeDb creates a db instance for testing. // makeDb creates a db instance for testing.
makeDb := func() ethdb.Database { makeDb := func() ethdb.Database {
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
if err != nil { if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err) t.Fatalf("failed to create temp freezer db: %v", err)
} }
@@ -1712,7 +1712,7 @@ func TestTrieForkGC(t *testing.T) {
Config: params.TestChainConfig, Config: params.TestChainConfig,
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
} }
genDb, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*TriesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) genDb, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*state.TriesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) })
// Generate a bunch of fork blocks, each side forking from the canonical chain // Generate a bunch of fork blocks, each side forking from the canonical chain
forks := make([]*types.Block, len(blocks)) forks := make([]*types.Block, len(blocks))
@@ -1740,7 +1740,7 @@ func TestTrieForkGC(t *testing.T) {
} }
} }
// Dereference all the recent tries and ensure no past trie is left in // Dereference all the recent tries and ensure no past trie is left in
for i := 0; i < TriesInMemory; i++ { for i := 0; i < state.TriesInMemory; i++ {
chain.TrieDB().Dereference(blocks[len(blocks)-1-i].Root()) chain.TrieDB().Dereference(blocks[len(blocks)-1-i].Root())
chain.TrieDB().Dereference(forks[len(blocks)-1-i].Root()) chain.TrieDB().Dereference(forks[len(blocks)-1-i].Root())
} }
@@ -1764,11 +1764,11 @@ func testLargeReorgTrieGC(t *testing.T, scheme string) {
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
} }
genDb, shared, _ := GenerateChainWithGenesis(genesis, engine, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) genDb, shared, _ := GenerateChainWithGenesis(genesis, engine, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) })
original, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*TriesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{2}) }) original, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*state.TriesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{2}) })
competitor, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*TriesInMemory+1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{3}) }) competitor, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*state.TriesInMemory+1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{3}) })
// Import the shared chain and the original canonical one // Import the shared chain and the original canonical one
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
defer db.Close() defer db.Close()
chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil)
@@ -1804,7 +1804,7 @@ func testLargeReorgTrieGC(t *testing.T, scheme string) {
} }
// In path-based trie database implementation, it will keep 128 diff + 1 disk // In path-based trie database implementation, it will keep 128 diff + 1 disk
// layers, totally 129 latest states available. In hash-based it's 128. // layers, totally 129 latest states available. In hash-based it's 128.
states := TriesInMemory states := state.TriesInMemory
if scheme == rawdb.PathScheme { if scheme == rawdb.PathScheme {
states = states + 1 states = states + 1
} }
@@ -1833,7 +1833,7 @@ func testBlockchainRecovery(t *testing.T, scheme string) {
funds = big.NewInt(1000000000) funds = big.NewInt(1000000000)
gspec = &Genesis{Config: params.TestChainConfig, Alloc: types.GenesisAlloc{address: {Balance: funds}}} gspec = &Genesis{Config: params.TestChainConfig, Alloc: types.GenesisAlloc{address: {Balance: funds}}}
) )
height := uint64(1024) height := uint64(64)
_, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil) _, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil)
// Import the chain as a ancient-first node and ensure all pointers are updated // Import the chain as a ancient-first node and ensure all pointers are updated
@@ -1908,7 +1908,7 @@ func testInsertReceiptChainRollback(t *testing.T, scheme string) {
} }
// Set up a BlockChain that uses the ancient store. // Set up a BlockChain that uses the ancient store.
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
if err != nil { if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err) t.Fatalf("failed to create temp freezer db: %v", err)
} }
@@ -1972,13 +1972,13 @@ func testLowDiffLongChain(t *testing.T, scheme string) {
} }
// We must use a pretty long chain to ensure that the fork doesn't overtake us // We must use a pretty long chain to ensure that the fork doesn't overtake us
// until after at least 128 blocks post tip // until after at least 128 blocks post tip
genDb, blocks, _ := GenerateChainWithGenesis(genesis, engine, 6*TriesInMemory, func(i int, b *BlockGen) { genDb, blocks, _ := GenerateChainWithGenesis(genesis, engine, 6*state.TriesInMemory, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{1}) b.SetCoinbase(common.Address{1})
b.OffsetTime(-9) b.OffsetTime(-9)
}) })
// Import the canonical chain // Import the canonical chain
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
defer diskdb.Close() defer diskdb.Close()
chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil)
@@ -1992,7 +1992,7 @@ func testLowDiffLongChain(t *testing.T, scheme string) {
} }
// Generate fork chain, starting from an early block // Generate fork chain, starting from an early block
parent := blocks[10] parent := blocks[10]
fork, _ := GenerateChain(genesis.Config, parent, engine, genDb, 8*TriesInMemory, func(i int, b *BlockGen) { fork, _ := GenerateChain(genesis.Config, parent, engine, genDb, 8*state.TriesInMemory, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{2}) b.SetCoinbase(common.Address{2})
}) })
@@ -2055,7 +2055,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
// Set the terminal total difficulty in the config // Set the terminal total difficulty in the config
gspec.Config.TerminalTotalDifficulty = big.NewInt(0) gspec.Config.TerminalTotalDifficulty = big.NewInt(0)
} }
genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, 2*TriesInMemory, func(i int, gen *BlockGen) { genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, 2*state.TriesInMemory, func(i int, gen *BlockGen) {
tx, err := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("deadbeef"), big.NewInt(100), 21000, big.NewInt(int64(i+1)*params.GWei), nil), signer, key) tx, err := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("deadbeef"), big.NewInt(100), 21000, big.NewInt(int64(i+1)*params.GWei), nil), signer, key)
if err != nil { if err != nil {
t.Fatalf("failed to create tx: %v", err) t.Fatalf("failed to create tx: %v", err)
@@ -2070,9 +2070,9 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
t.Fatalf("block %d: failed to insert into chain: %v", n, err) t.Fatalf("block %d: failed to insert into chain: %v", n, err)
} }
lastPrunedIndex := len(blocks) - TriesInMemory - 1 lastPrunedIndex := len(blocks) - state.TriesInMemory - 1
lastPrunedBlock := blocks[lastPrunedIndex] lastPrunedBlock := blocks[lastPrunedIndex]
firstNonPrunedBlock := blocks[len(blocks)-TriesInMemory] firstNonPrunedBlock := blocks[len(blocks)-state.TriesInMemory]
// Verify pruning of lastPrunedBlock // Verify pruning of lastPrunedBlock
if chain.HasBlockAndState(lastPrunedBlock.Hash(), lastPrunedBlock.NumberU64()) { if chain.HasBlockAndState(lastPrunedBlock.Hash(), lastPrunedBlock.NumberU64()) {
@@ -2099,7 +2099,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
// Generate fork chain, make it longer than canon // Generate fork chain, make it longer than canon
parentIndex := lastPrunedIndex + blocksBetweenCommonAncestorAndPruneblock parentIndex := lastPrunedIndex + blocksBetweenCommonAncestorAndPruneblock
parent := blocks[parentIndex] parent := blocks[parentIndex]
fork, _ := GenerateChain(gspec.Config, parent, engine, genDb, 2*TriesInMemory, func(i int, b *BlockGen) { fork, _ := GenerateChain(gspec.Config, parent, engine, genDb, 2*state.TriesInMemory, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{2}) b.SetCoinbase(common.Address{2})
if int(b.header.Number.Uint64()) >= mergeBlock { if int(b.header.Number.Uint64()) >= mergeBlock {
b.SetPoS() b.SetPoS()
@@ -2190,7 +2190,7 @@ func testInsertKnownChainData(t *testing.T, typ string, scheme string) {
b.OffsetTime(-9) // A higher difficulty b.OffsetTime(-9) // A higher difficulty
}) })
// Import the shared chain and the original canonical one // Import the shared chain and the original canonical one
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
if err != nil { if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err) t.Fatalf("failed to create temp freezer db: %v", err)
} }
@@ -2361,7 +2361,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i
} }
}) })
// Import the shared chain and the original canonical one // Import the shared chain and the original canonical one
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
if err != nil { if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err) t.Fatalf("failed to create temp freezer db: %v", err)
} }
@@ -2742,7 +2742,7 @@ func testSideImportPrunedBlocks(t *testing.T, scheme string) {
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
} }
// Generate and import the canonical chain // Generate and import the canonical chain
_, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*TriesInMemory, nil) _, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*state.TriesInMemory, nil)
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil)
if err != nil { if err != nil {
@@ -2755,9 +2755,9 @@ func testSideImportPrunedBlocks(t *testing.T, scheme string) {
} }
// In path-based trie database implementation, it will keep 128 diff + 1 disk // In path-based trie database implementation, it will keep 128 diff + 1 disk
// layers, totally 129 latest states available. In hash-based it's 128. // layers, totally 129 latest states available. In hash-based it's 128.
states := TriesInMemory states := state.TriesInMemory
if scheme == rawdb.PathScheme { if scheme == rawdb.PathScheme {
states = TriesInMemory + 1 states = state.TriesInMemory + 1
} }
lastPrunedIndex := len(blocks) - states - 1 lastPrunedIndex := len(blocks) - states - 1
lastPrunedBlock := blocks[lastPrunedIndex] lastPrunedBlock := blocks[lastPrunedIndex]
@@ -3634,18 +3634,19 @@ func testSetCanonical(t *testing.T, scheme string) {
Alloc: types.GenesisAlloc{address: {Balance: funds}}, Alloc: types.GenesisAlloc{address: {Balance: funds}},
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
} }
signer = types.LatestSigner(gspec.Config) signer = types.LatestSigner(gspec.Config)
engine = ethash.NewFaker() engine = ethash.NewFaker()
chainLength = 10
) )
// Generate and import the canonical chain // Generate and import the canonical chain
_, canon, _ := GenerateChainWithGenesis(gspec, engine, 2*TriesInMemory, func(i int, gen *BlockGen) { _, canon, _ := GenerateChainWithGenesis(gspec, engine, chainLength, func(i int, gen *BlockGen) {
tx, err := types.SignTx(types.NewTransaction(gen.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key) tx, err := types.SignTx(types.NewTransaction(gen.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key)
if err != nil { if err != nil {
panic(err) panic(err)
} }
gen.AddTx(tx) gen.AddTx(tx)
}) })
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
defer diskdb.Close() defer diskdb.Close()
chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil)
@@ -3659,7 +3660,7 @@ func testSetCanonical(t *testing.T, scheme string) {
} }
// Generate the side chain and import them // Generate the side chain and import them
_, side, _ := GenerateChainWithGenesis(gspec, engine, 2*TriesInMemory, func(i int, gen *BlockGen) { _, side, _ := GenerateChainWithGenesis(gspec, engine, chainLength, func(i int, gen *BlockGen) {
tx, err := types.SignTx(types.NewTransaction(gen.TxNonce(address), common.Address{0x00}, big.NewInt(1), params.TxGas, gen.header.BaseFee, nil), signer, key) tx, err := types.SignTx(types.NewTransaction(gen.TxNonce(address), common.Address{0x00}, big.NewInt(1), params.TxGas, gen.header.BaseFee, nil), signer, key)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -3698,8 +3699,8 @@ func testSetCanonical(t *testing.T, scheme string) {
verify(side[len(side)-1]) verify(side[len(side)-1])
// Reset the chain head to original chain // Reset the chain head to original chain
chain.SetCanonical(canon[TriesInMemory-1]) chain.SetCanonical(canon[chainLength-1])
verify(canon[TriesInMemory-1]) verify(canon[chainLength-1])
} }
// TestCanonicalHashMarker tests all the canonical hash markers are updated/deleted // TestCanonicalHashMarker tests all the canonical hash markers are updated/deleted

View File

@@ -476,7 +476,7 @@ func (g *Genesis) ToBlock() *types.Block {
} }
} }
} }
return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)).WithWithdrawals(withdrawals) return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil))
} }
// Commit writes the block and state of a genesis specification to the database. // Commit writes the block and state of a genesis specification to the database.

View File

@@ -322,7 +322,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
t.Fatalf("expected trie to be verkle") t.Fatalf("expected trie to be verkle")
} }
if !rawdb.ExistsAccountTrieNode(db, nil) { if !rawdb.HasAccountTrieNode(db, nil) {
t.Fatal("could not find node") t.Fatal("could not find node")
} }
} }

View File

@@ -101,6 +101,7 @@ func main() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer file.Close()
if err := json.NewDecoder(file).Decode(g); err != nil { if err := json.NewDecoder(file).Decode(g); err != nil {
panic(err) panic(err)
} }

View File

@@ -753,7 +753,7 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block {
if body == nil { if body == nil {
return nil return nil
} }
return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles).WithWithdrawals(body.Withdrawals) return types.NewBlockWithHeader(header).WithBody(*body)
} }
// WriteBlock serializes a block into the database, header and body separately. // WriteBlock serializes a block into the database, header and body separately.
@@ -843,7 +843,11 @@ func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block {
} }
for _, bad := range badBlocks { for _, bad := range badBlocks {
if bad.Header.Hash() == hash { if bad.Header.Hash() == hash {
return types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles).WithWithdrawals(bad.Body.Withdrawals) block := types.NewBlockWithHeader(bad.Header)
if bad.Body != nil {
block = block.WithBody(*bad.Body)
}
return block
} }
} }
return nil return nil
@@ -862,7 +866,11 @@ func ReadAllBadBlocks(db ethdb.Reader) []*types.Block {
} }
var blocks []*types.Block var blocks []*types.Block
for _, bad := range badBlocks { for _, bad := range badBlocks {
blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles).WithWithdrawals(bad.Body.Withdrawals)) block := types.NewBlockWithHeader(bad.Header)
if bad.Body != nil {
block = block.WithBody(*bad.Body)
}
blocks = append(blocks, block)
} }
return blocks return blocks
} }

View File

@@ -640,7 +640,7 @@ func makeTestBlocks(nblock int, txsPerBlock int) []*types.Block {
Number: big.NewInt(int64(i)), Number: big.NewInt(int64(i)),
Extra: []byte("test block"), Extra: []byte("test block"),
} }
blocks[i] = types.NewBlockWithHeader(header).WithBody(txs, nil) blocks[i] = types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs})
blocks[i].Hash() // pre-cache the block hash blocks[i].Hash() // pre-cache the block hash
} }
return blocks return blocks

View File

@@ -76,7 +76,7 @@ func TestLookupStorage(t *testing.T) {
tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}) tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33})
txs := []*types.Transaction{tx1, tx2, tx3} txs := []*types.Transaction{tx1, tx2, tx3}
block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil, newTestHasher()) block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, &types.Body{Transactions: txs}, nil, newTestHasher())
// Check that no transactions entries are in a pristine database // Check that no transactions entries are in a pristine database
for i, tx := range txs { for i, tx := range txs {

View File

@@ -24,7 +24,6 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"golang.org/x/crypto/sha3"
) )
// HashScheme is the legacy hash-based state scheme with which trie nodes are // HashScheme is the legacy hash-based state scheme with which trie nodes are
@@ -50,7 +49,7 @@ const PathScheme = "path"
type hasher struct{ sha crypto.KeccakState } type hasher struct{ sha crypto.KeccakState }
var hasherPool = sync.Pool{ var hasherPool = sync.Pool{
New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} }, New: func() interface{} { return &hasher{sha: crypto.NewKeccakState()} },
} }
func newHasher() *hasher { func newHasher() *hasher {
@@ -65,33 +64,15 @@ func (h *hasher) release() {
hasherPool.Put(h) hasherPool.Put(h)
} }
// ReadAccountTrieNode retrieves the account trie node and the associated node // ReadAccountTrieNode retrieves the account trie node with the specified node path.
// hash with the specified node path. func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) []byte {
func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) ([]byte, common.Hash) { data, _ := db.Get(accountTrieNodeKey(path))
data, err := db.Get(accountTrieNodeKey(path)) return data
if err != nil {
return nil, common.Hash{}
}
h := newHasher()
defer h.release()
return data, h.hash(data)
} }
// HasAccountTrieNode checks the account trie node presence with the specified // HasAccountTrieNode checks the presence of the account trie node with the
// node path and the associated node hash.
func HasAccountTrieNode(db ethdb.KeyValueReader, path []byte, hash common.Hash) bool {
data, err := db.Get(accountTrieNodeKey(path))
if err != nil {
return false
}
h := newHasher()
defer h.release()
return h.hash(data) == hash
}
// ExistsAccountTrieNode checks the presence of the account trie node with the
// specified node path, regardless of the node hash. // specified node path, regardless of the node hash.
func ExistsAccountTrieNode(db ethdb.KeyValueReader, path []byte) bool { func HasAccountTrieNode(db ethdb.KeyValueReader, path []byte) bool {
has, err := db.Has(accountTrieNodeKey(path)) has, err := db.Has(accountTrieNodeKey(path))
if err != nil { if err != nil {
return false return false
@@ -113,33 +94,15 @@ func DeleteAccountTrieNode(db ethdb.KeyValueWriter, path []byte) {
} }
} }
// ReadStorageTrieNode retrieves the storage trie node and the associated node // ReadStorageTrieNode retrieves the storage trie node with the specified node path.
// hash with the specified node path. func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) []byte {
func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) ([]byte, common.Hash) { data, _ := db.Get(storageTrieNodeKey(accountHash, path))
data, err := db.Get(storageTrieNodeKey(accountHash, path)) return data
if err != nil {
return nil, common.Hash{}
}
h := newHasher()
defer h.release()
return data, h.hash(data)
} }
// HasStorageTrieNode checks the storage trie node presence with the provided // HasStorageTrieNode checks the presence of the storage trie node with the
// node path and the associated node hash.
func HasStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte, hash common.Hash) bool {
data, err := db.Get(storageTrieNodeKey(accountHash, path))
if err != nil {
return false
}
h := newHasher()
defer h.release()
return h.hash(data) == hash
}
// ExistsStorageTrieNode checks the presence of the storage trie node with the
// specified account hash and node path, regardless of the node hash. // specified account hash and node path, regardless of the node hash.
func ExistsStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) bool { func HasStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) bool {
has, err := db.Has(storageTrieNodeKey(accountHash, path)) has, err := db.Has(storageTrieNodeKey(accountHash, path))
if err != nil { if err != nil {
return false return false
@@ -198,10 +161,18 @@ func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash c
case HashScheme: case HashScheme:
return HasLegacyTrieNode(db, hash) return HasLegacyTrieNode(db, hash)
case PathScheme: case PathScheme:
var blob []byte
if owner == (common.Hash{}) { if owner == (common.Hash{}) {
return HasAccountTrieNode(db, path, hash) blob = ReadAccountTrieNode(db, path)
} else {
blob = ReadStorageTrieNode(db, owner, path)
} }
return HasStorageTrieNode(db, owner, path, hash) if len(blob) == 0 {
return false
}
h := newHasher()
defer h.release()
return h.hash(blob) == hash // exists but not match
default: default:
panic(fmt.Sprintf("Unknown scheme %v", scheme)) panic(fmt.Sprintf("Unknown scheme %v", scheme))
} }
@@ -209,43 +180,35 @@ func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash c
// ReadTrieNode retrieves the trie node from database with the provided node info // ReadTrieNode retrieves the trie node from database with the provided node info
// and associated node hash. // and associated node hash.
// hashScheme-based lookup requires the following:
// - hash
//
// pathScheme-based lookup requires the following:
// - owner
// - path
func ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash, scheme string) []byte { func ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash, scheme string) []byte {
switch scheme { switch scheme {
case HashScheme: case HashScheme:
return ReadLegacyTrieNode(db, hash) return ReadLegacyTrieNode(db, hash)
case PathScheme: case PathScheme:
var ( var blob []byte
blob []byte
nHash common.Hash
)
if owner == (common.Hash{}) { if owner == (common.Hash{}) {
blob, nHash = ReadAccountTrieNode(db, path) blob = ReadAccountTrieNode(db, path)
} else { } else {
blob, nHash = ReadStorageTrieNode(db, owner, path) blob = ReadStorageTrieNode(db, owner, path)
} }
if nHash != hash { if len(blob) == 0 {
return nil return nil
} }
h := newHasher()
defer h.release()
if h.hash(blob) != hash {
return nil // exists but not match
}
return blob return blob
default: default:
panic(fmt.Sprintf("Unknown scheme %v", scheme)) panic(fmt.Sprintf("Unknown scheme %v", scheme))
} }
} }
// WriteTrieNode writes the trie node into database with the provided node info // WriteTrieNode writes the trie node into database with the provided node info.
// and associated node hash.
// hashScheme-based lookup requires the following:
// - hash
// //
// pathScheme-based lookup requires the following: // hash-scheme requires the node hash as the identifier.
// - owner // path-scheme requires the node owner and path as the identifier.
// - path
func WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, node []byte, scheme string) { func WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, node []byte, scheme string) {
switch scheme { switch scheme {
case HashScheme: case HashScheme:
@@ -261,14 +224,10 @@ func WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash
} }
} }
// DeleteTrieNode deletes the trie node from database with the provided node info // DeleteTrieNode deletes the trie node from database with the provided node info.
// and associated node hash.
// hashScheme-based lookup requires the following:
// - hash
// //
// pathScheme-based lookup requires the following: // hash-scheme requires the node hash as the identifier.
// - owner // path-scheme requires the node owner and path as the identifier.
// - path
func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, scheme string) { func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, scheme string) {
switch scheme { switch scheme {
case HashScheme: case HashScheme:
@@ -287,9 +246,8 @@ func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, has
// ReadStateScheme reads the state scheme of persistent state, or none // ReadStateScheme reads the state scheme of persistent state, or none
// if the state is not present in database. // if the state is not present in database.
func ReadStateScheme(db ethdb.Reader) string { func ReadStateScheme(db ethdb.Reader) string {
// Check if state in path-based scheme is present // Check if state in path-based scheme is present.
blob, _ := ReadAccountTrieNode(db, nil) if HasAccountTrieNode(db, nil) {
if len(blob) != 0 {
return PathScheme return PathScheme
} }
// The root node might be deleted during the initial snap sync, check // The root node might be deleted during the initial snap sync, check
@@ -304,8 +262,7 @@ func ReadStateScheme(db ethdb.Reader) string {
if header == nil { if header == nil {
return "" // empty datadir return "" // empty datadir
} }
blob = ReadLegacyTrieNode(db, header.Root) if !HasLegacyTrieNode(db, header.Root) {
if len(blob) == 0 {
return "" // no state in disk return "" // no state in disk
} }
return HashScheme return HashScheme

View File

@@ -16,7 +16,11 @@
package rawdb package rawdb
import "path/filepath" import (
"path/filepath"
"github.com/ethereum/go-ethereum/ethdb"
)
// The list of table names of chain freezer. // The list of table names of chain freezer.
const ( const (
@@ -75,7 +79,15 @@ var (
// freezers the collections of all builtin freezers. // freezers the collections of all builtin freezers.
var freezers = []string{ChainFreezerName, StateFreezerName} var freezers = []string{ChainFreezerName, StateFreezerName}
// NewStateFreezer initializes the freezer for state history. // NewStateFreezer initializes the ancient store for state history.
func NewStateFreezer(ancientDir string, readOnly bool) (*ResettableFreezer, error) { //
return NewResettableFreezer(filepath.Join(ancientDir, StateFreezerName), "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy) // - if the empty directory is given, initializes the pure in-memory
// state freezer (e.g. dev mode).
// - if non-empty directory is given, initializes the regular file-based
// state freezer.
func NewStateFreezer(ancientDir string, readOnly bool) (ethdb.ResettableAncientStore, error) {
if ancientDir == "" {
return NewMemoryFreezer(readOnly, stateFreezerNoSnappy), nil
}
return newResettableFreezer(filepath.Join(ancientDir, StateFreezerName), "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy)
} }

View File

@@ -89,20 +89,17 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) {
infos = append(infos, info) infos = append(infos, info)
case StateFreezerName: case StateFreezerName:
if ReadStateScheme(db) != PathScheme {
continue
}
datadir, err := db.AncientDatadir() datadir, err := db.AncientDatadir()
if err != nil { if err != nil {
return nil, err return nil, err
} }
f, err := NewStateFreezer(datadir, true) f, err := NewStateFreezer(datadir, true)
if err != nil { if err != nil {
return nil, err continue // might be possible the state freezer is not existent
} }
defer f.Close() defer f.Close()
info, err := inspect(StateFreezerName, stateFreezerNoSnappy, f) info, err := inspect(freezer, stateFreezerNoSnappy, f)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -0,0 +1,325 @@
// 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 ancienttest
import (
"bytes"
"reflect"
"testing"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/testrand"
)
// TestAncientSuite runs a suite of tests against an ancient database
// implementation.
func TestAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
// Test basic read methods
t.Run("BasicRead", func(t *testing.T) { basicRead(t, newFn) })
// Test batch read method
t.Run("BatchRead", func(t *testing.T) { batchRead(t, newFn) })
// Test basic write methods
t.Run("BasicWrite", func(t *testing.T) { basicWrite(t, newFn) })
// Test if data mutation is allowed after db write
t.Run("nonMutable", func(t *testing.T) { nonMutable(t, newFn) })
}
func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
var (
db = newFn([]string{"a"})
data = makeDataset(100, 32)
)
defer db.Close()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < len(data); i++ {
op.AppendRaw("a", uint64(i), data[i])
}
return nil
})
db.TruncateTail(10)
db.TruncateHead(90)
// Test basic tail and head retrievals
tail, err := db.Tail()
if err != nil || tail != 10 {
t.Fatal("Failed to retrieve tail")
}
ancient, err := db.Ancients()
if err != nil || ancient != 90 {
t.Fatal("Failed to retrieve ancient")
}
// Test the deleted items shouldn't be reachable
var cases = []struct {
start int
limit int
}{
{0, 10},
{90, 100},
}
for _, c := range cases {
for i := c.start; i < c.limit; i++ {
exist, err := db.HasAncient("a", uint64(i))
if err != nil {
t.Fatalf("Failed to check presence, %v", err)
}
if exist {
t.Fatalf("Item %d is already truncated", uint64(i))
}
_, err = db.Ancient("a", uint64(i))
if err == nil {
t.Fatal("Error is expected for non-existent item")
}
}
}
// Test the items in range should be reachable
for i := 10; i < 90; i++ {
exist, err := db.HasAncient("a", uint64(i))
if err != nil {
t.Fatalf("Failed to check presence, %v", err)
}
if !exist {
t.Fatalf("Item %d is missing", uint64(i))
}
blob, err := db.Ancient("a", uint64(i))
if err != nil {
t.Fatalf("Failed to retrieve item, %v", err)
}
if !bytes.Equal(blob, data[i]) {
t.Fatalf("Unexpected item content, want: %v, got: %v", data[i], blob)
}
}
// Test the items in unknown table shouldn't be reachable
exist, err := db.HasAncient("b", uint64(0))
if err != nil {
t.Fatalf("Failed to check presence, %v", err)
}
if exist {
t.Fatal("Item in unknown table shouldn't be found")
}
_, err = db.Ancient("b", uint64(0))
if err == nil {
t.Fatal("Error is expected for unknown table")
}
}
func batchRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
var (
db = newFn([]string{"a"})
data = makeDataset(100, 32)
)
defer db.Close()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), data[i])
}
return nil
})
db.TruncateTail(10)
db.TruncateHead(90)
// Test the items in range should be reachable
var cases = []struct {
start uint64
count uint64
maxSize uint64
expStart int
expLimit int
}{
// Items in range [10, 90) with no size limitation
{
10, 80, 0, 10, 90,
},
// Items in range [10, 90) with 32 size cap, single item is expected
{
10, 80, 32, 10, 11,
},
// Items in range [10, 90) with 31 size cap, single item is expected
{
10, 80, 31, 10, 11,
},
// Items in range [10, 90) with 32*80 size cap, all items are expected
{
10, 80, 32 * 80, 10, 90,
},
// Extra items above the last item are not returned
{
10, 90, 0, 10, 90,
},
}
for i, c := range cases {
batch, err := db.AncientRange("a", c.start, c.count, c.maxSize)
if err != nil {
t.Fatalf("Failed to retrieve item in range, %v", err)
}
if !reflect.DeepEqual(batch, data[c.expStart:c.expLimit]) {
t.Fatalf("Case %d, Batch content is not matched", i)
}
}
// Test out-of-range / zero-size retrieval should be rejected
_, err := db.AncientRange("a", 0, 1, 0)
if err == nil {
t.Fatal("Out-of-range retrieval should be rejected")
}
_, err = db.AncientRange("a", 90, 1, 0)
if err == nil {
t.Fatal("Out-of-range retrieval should be rejected")
}
_, err = db.AncientRange("a", 10, 0, 0)
if err == nil {
t.Fatal("Zero-size retrieval should be rejected")
}
// Test item in unknown table shouldn't be reachable
_, err = db.AncientRange("b", 10, 1, 0)
if err == nil {
t.Fatal("Item in unknown table shouldn't be found")
}
}
func basicWrite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
var (
db = newFn([]string{"a", "b"})
dataA = makeDataset(100, 32)
dataB = makeDataset(100, 32)
)
defer db.Close()
// The ancient write to tables should be aligned
_, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i])
}
return nil
})
if err == nil {
t.Fatal("Unaligned ancient write should be rejected")
}
// Test normal ancient write
size, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i])
op.AppendRaw("b", uint64(i), dataB[i])
}
return nil
})
if err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
wantSize := int64(6400)
if size != wantSize {
t.Fatalf("Ancient write size is not expected, want: %d, got: %d", wantSize, size)
}
// Write should work after head truncating
db.TruncateHead(90)
_, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 90; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i])
op.AppendRaw("b", uint64(i), dataB[i])
}
return nil
})
if err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
// Write should work after truncating everything
db.TruncateTail(0)
_, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i])
op.AppendRaw("b", uint64(i), dataB[i])
}
return nil
})
if err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
}
func nonMutable(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
db := newFn([]string{"a"})
defer db.Close()
// We write 100 zero-bytes to the freezer and immediately mutate the slice
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
data := make([]byte, 100)
op.AppendRaw("a", uint64(0), data)
for i := range data {
data[i] = 0xff
}
return nil
})
// Now read it.
data, err := db.Ancient("a", uint64(0))
if err != nil {
t.Fatal(err)
}
for k, v := range data {
if v != 0 {
t.Fatalf("byte %d != 0: %x", k, v)
}
}
}
// TestResettableAncientSuite runs a suite of tests against a resettable ancient
// database implementation.
func TestResettableAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.ResettableAncientStore) {
t.Run("Reset", func(t *testing.T) {
var (
db = newFn([]string{"a"})
data = makeDataset(100, 32)
)
defer db.Close()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), data[i])
}
return nil
})
db.TruncateTail(10)
db.TruncateHead(90)
// Ancient write should work after resetting
db.Reset()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), data[i])
}
return nil
})
})
}
func makeDataset(size, value int) [][]byte {
var vals [][]byte
for i := 0; i < size; i += 1 {
vals = append(vals, testrand.Bytes(value))
}
return vals
}

View File

@@ -39,26 +39,40 @@ const (
freezerBatchLimit = 30000 freezerBatchLimit = 30000
) )
// chainFreezer is a wrapper of freezer with additional chain freezing feature. // chainFreezer is a wrapper of chain ancient store with additional chain freezing
// The background thread will keep moving ancient chain segments from key-value // feature. The background thread will keep moving ancient chain segments from
// database to flat files for saving space on live database. // key-value database to flat files for saving space on live database.
type chainFreezer struct { type chainFreezer struct {
*Freezer ethdb.AncientStore // Ancient store for storing cold chain segment
quit chan struct{} quit chan struct{}
wg sync.WaitGroup wg sync.WaitGroup
trigger chan chan struct{} // Manual blocking freeze trigger, test determinism trigger chan chan struct{} // Manual blocking freeze trigger, test determinism
} }
// newChainFreezer initializes the freezer for ancient chain data. // newChainFreezer initializes the freezer for ancient chain segment.
//
// - if the empty directory is given, initializes the pure in-memory
// state freezer (e.g. dev mode).
// - if non-empty directory is given, initializes the regular file-based
// state freezer.
func newChainFreezer(datadir string, namespace string, readonly bool) (*chainFreezer, error) { func newChainFreezer(datadir string, namespace string, readonly bool) (*chainFreezer, error) {
freezer, err := NewChainFreezer(datadir, namespace, readonly) var (
err error
freezer ethdb.AncientStore
)
if datadir == "" {
freezer = NewMemoryFreezer(readonly, chainFreezerNoSnappy)
} else {
freezer, err = NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerNoSnappy)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &chainFreezer{ return &chainFreezer{
Freezer: freezer, AncientStore: freezer,
quit: make(chan struct{}), quit: make(chan struct{}),
trigger: make(chan chan struct{}), trigger: make(chan chan struct{}),
}, nil }, nil
} }
@@ -70,7 +84,7 @@ func (f *chainFreezer) Close() error {
close(f.quit) close(f.quit)
} }
f.wg.Wait() f.wg.Wait()
return f.Freezer.Close() return f.AncientStore.Close()
} }
// readHeadNumber returns the number of chain head block. 0 is returned if the // readHeadNumber returns the number of chain head block. 0 is returned if the
@@ -167,7 +181,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
log.Debug("Current full block not old enough to freeze", "err", err) log.Debug("Current full block not old enough to freeze", "err", err)
continue continue
} }
frozen := f.frozen.Load() frozen, _ := f.Ancients() // no error will occur, safe to ignore
// Short circuit if the blocks below threshold are already frozen. // Short circuit if the blocks below threshold are already frozen.
if frozen != 0 && frozen-1 >= threshold { if frozen != 0 && frozen-1 >= threshold {
@@ -190,7 +204,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
backoff = true backoff = true
continue continue
} }
// Batch of blocks have been frozen, flush them before wiping from leveldb // Batch of blocks have been frozen, flush them before wiping from key-value store
if err := f.Sync(); err != nil { if err := f.Sync(); err != nil {
log.Crit("Failed to flush frozen tables", "err", err) log.Crit("Failed to flush frozen tables", "err", err)
} }
@@ -210,7 +224,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
// Wipe out side chains also and track dangling side chains // Wipe out side chains also and track dangling side chains
var dangling []common.Hash var dangling []common.Hash
frozen = f.frozen.Load() // Needs reload after during freezeRange frozen, _ = f.Ancients() // Needs reload after during freezeRange
for number := first; number < frozen; number++ { for number := first; number < frozen; number++ {
// Always keep the genesis block in active database // Always keep the genesis block in active database
if number != 0 { if number != 0 {

View File

@@ -34,7 +34,7 @@ func TestChainIterator(t *testing.T) {
var block *types.Block var block *types.Block
var txs []*types.Transaction var txs []*types.Transaction
to := common.BytesToAddress([]byte{0x11}) to := common.BytesToAddress([]byte{0x11})
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newTestHasher()) // Empty genesis block block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher()) // Empty genesis block
WriteBlock(chainDb, block) WriteBlock(chainDb, block)
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
for i := uint64(1); i <= 10; i++ { for i := uint64(1); i <= 10; i++ {
@@ -60,7 +60,7 @@ func TestChainIterator(t *testing.T) {
}) })
} }
txs = append(txs, tx) txs = append(txs, tx)
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newTestHasher()) block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher())
WriteBlock(chainDb, block) WriteBlock(chainDb, block)
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
} }
@@ -111,7 +111,7 @@ func TestIndexTransactions(t *testing.T) {
to := common.BytesToAddress([]byte{0x11}) to := common.BytesToAddress([]byte{0x11})
// Write empty genesis block // Write empty genesis block
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newTestHasher()) block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher())
WriteBlock(chainDb, block) WriteBlock(chainDb, block)
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
@@ -138,7 +138,7 @@ func TestIndexTransactions(t *testing.T) {
}) })
} }
txs = append(txs, tx) txs = append(txs, tx)
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newTestHasher()) block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher())
WriteBlock(chainDb, block) WriteBlock(chainDb, block)
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
} }

View File

@@ -34,11 +34,13 @@ import (
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
) )
// freezerdb is a database wrapper that enables freezer data retrievals. // freezerdb is a database wrapper that enables ancient chain segment freezing.
type freezerdb struct { type freezerdb struct {
ancientRoot string
ethdb.KeyValueStore ethdb.KeyValueStore
ethdb.AncientStore *chainFreezer
readOnly bool
ancientRoot string
} }
// AncientDatadir returns the path of root ancient directory. // AncientDatadir returns the path of root ancient directory.
@@ -50,7 +52,7 @@ func (frdb *freezerdb) AncientDatadir() (string, error) {
// the slow ancient tables. // the slow ancient tables.
func (frdb *freezerdb) Close() error { func (frdb *freezerdb) Close() error {
var errs []error var errs []error
if err := frdb.AncientStore.Close(); err != nil { if err := frdb.chainFreezer.Close(); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
if err := frdb.KeyValueStore.Close(); err != nil { if err := frdb.KeyValueStore.Close(); err != nil {
@@ -66,12 +68,12 @@ func (frdb *freezerdb) Close() error {
// a freeze cycle completes, without having to sleep for a minute to trigger the // a freeze cycle completes, without having to sleep for a minute to trigger the
// automatic background run. // automatic background run.
func (frdb *freezerdb) Freeze() error { func (frdb *freezerdb) Freeze() error {
if frdb.AncientStore.(*chainFreezer).readonly { if frdb.readOnly {
return errReadOnly return errReadOnly
} }
// Trigger a freeze cycle and block until it's done // Trigger a freeze cycle and block until it's done
trigger := make(chan struct{}, 1) trigger := make(chan struct{}, 1)
frdb.AncientStore.(*chainFreezer).trigger <- trigger frdb.chainFreezer.trigger <- trigger
<-trigger <-trigger
return nil return nil
} }
@@ -192,8 +194,14 @@ func resolveChainFreezerDir(ancient string) string {
// storage. The passed ancient indicates the path of root ancient directory // storage. The passed ancient indicates the path of root ancient directory
// where the chain freezer can be opened. // where the chain freezer can be opened.
func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace string, readonly bool) (ethdb.Database, error) { func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace string, readonly bool) (ethdb.Database, error) {
// Create the idle freezer instance // Create the idle freezer instance. If the given ancient directory is empty,
frdb, err := newChainFreezer(resolveChainFreezerDir(ancient), namespace, readonly) // in-memory chain freezer is used (e.g. dev mode); otherwise the regular
// file-based freezer is created.
chainFreezerDir := ancient
if chainFreezerDir != "" {
chainFreezerDir = resolveChainFreezerDir(chainFreezerDir)
}
frdb, err := newChainFreezer(chainFreezerDir, namespace, readonly)
if err != nil { if err != nil {
printChainMetadata(db) printChainMetadata(db)
return nil, err return nil, err
@@ -277,7 +285,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
} }
} }
// Freezer is consistent with the key-value database, permit combining the two // Freezer is consistent with the key-value database, permit combining the two
if !frdb.readonly { if !readonly {
frdb.wg.Add(1) frdb.wg.Add(1)
go func() { go func() {
frdb.freeze(db) frdb.freeze(db)
@@ -287,7 +295,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
return &freezerdb{ return &freezerdb{
ancientRoot: ancient, ancientRoot: ancient,
KeyValueStore: db, KeyValueStore: db,
AncientStore: frdb, chainFreezer: frdb,
}, nil }, nil
} }

View File

@@ -62,7 +62,7 @@ const freezerTableSize = 2 * 1000 * 1000 * 1000
// reserving it for go-ethereum. This would also reduce the memory requirements // reserving it for go-ethereum. This would also reduce the memory requirements
// of Geth, and thus also GC overhead. // of Geth, and thus also GC overhead.
type Freezer struct { type Freezer struct {
frozen atomic.Uint64 // Number of blocks already frozen frozen atomic.Uint64 // Number of items already frozen
tail atomic.Uint64 // Number of the first stored item in the freezer tail atomic.Uint64 // Number of the first stored item in the freezer
// This lock synchronizes writers and the truncate operation, as well as // This lock synchronizes writers and the truncate operation, as well as
@@ -76,12 +76,6 @@ type Freezer struct {
closeOnce sync.Once closeOnce sync.Once
} }
// NewChainFreezer is a small utility method around NewFreezer that sets the
// default parameters for the chain storage.
func NewChainFreezer(datadir string, namespace string, readonly bool) (*Freezer, error) {
return NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerNoSnappy)
}
// NewFreezer creates a freezer instance for maintaining immutable ordered // NewFreezer creates a freezer instance for maintaining immutable ordered
// data according to the given parameters. // data according to the given parameters.
// //

View File

@@ -0,0 +1,428 @@
// 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 rawdb
import (
"errors"
"fmt"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
)
// memoryTable is used to store a list of sequential items in memory.
type memoryTable struct {
name string // Table name
items uint64 // Number of stored items in the table, including the deleted ones
offset uint64 // Number of deleted items from the table
data [][]byte // List of rlp-encoded items, sort in order
size uint64 // Total memory size occupied by the table
lock sync.RWMutex
}
// newMemoryTable initializes the memory table.
func newMemoryTable(name string) *memoryTable {
return &memoryTable{name: name}
}
// has returns an indicator whether the specified data exists.
func (t *memoryTable) has(number uint64) bool {
t.lock.RLock()
defer t.lock.RUnlock()
return number >= t.offset && number < t.items
}
// retrieve retrieves multiple items in sequence, starting from the index 'start'.
// It will return:
// - at most 'count' items,
// - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize),
// but will otherwise return as many items as fit into maxByteSize.
// - if maxBytes is not specified, 'count' items will be returned if they are present
func (t *memoryTable) retrieve(start uint64, count, maxBytes uint64) ([][]byte, error) {
t.lock.RLock()
defer t.lock.RUnlock()
var (
size uint64
batch [][]byte
)
// Ensure the start is written, not deleted from the tail, and that the
// caller actually wants something.
if t.items <= start || t.offset > start || count == 0 {
return nil, errOutOfBounds
}
// Cap the item count if the retrieval is out of bound.
if start+count > t.items {
count = t.items - start
}
for n := start; n < start+count; n++ {
index := n - t.offset
if len(batch) != 0 && maxBytes != 0 && size+uint64(len(t.data[index])) > maxBytes {
return batch, nil
}
batch = append(batch, t.data[index])
size += uint64(len(t.data[index]))
}
return batch, nil
}
// truncateHead discards any recent data above the provided threshold number.
func (t *memoryTable) truncateHead(items uint64) error {
t.lock.Lock()
defer t.lock.Unlock()
// Short circuit if nothing to delete.
if t.items <= items {
return nil
}
if items < t.offset {
return errors.New("truncation below tail")
}
t.data = t.data[:items-t.offset]
t.items = items
return nil
}
// truncateTail discards any recent data before the provided threshold number.
func (t *memoryTable) truncateTail(items uint64) error {
t.lock.Lock()
defer t.lock.Unlock()
// Short circuit if nothing to delete.
if t.offset >= items {
return nil
}
if t.items < items {
return errors.New("truncation above head")
}
t.data = t.data[items-t.offset:]
t.offset = items
return nil
}
// commit merges the given item batch into table. It's presumed that the
// batch is ordered and continuous with table.
func (t *memoryTable) commit(batch [][]byte) error {
t.lock.Lock()
defer t.lock.Unlock()
for _, item := range batch {
t.size += uint64(len(item))
}
t.data = append(t.data, batch...)
t.items += uint64(len(batch))
return nil
}
// memoryBatch is the singleton batch used for ancient write.
type memoryBatch struct {
data map[string][][]byte
next map[string]uint64
size map[string]int64
}
func newMemoryBatch() *memoryBatch {
return &memoryBatch{
data: make(map[string][][]byte),
next: make(map[string]uint64),
size: make(map[string]int64),
}
}
func (b *memoryBatch) reset(freezer *MemoryFreezer) {
b.data = make(map[string][][]byte)
b.next = make(map[string]uint64)
b.size = make(map[string]int64)
for name, table := range freezer.tables {
b.next[name] = table.items
}
}
// Append adds an RLP-encoded item.
func (b *memoryBatch) Append(kind string, number uint64, item interface{}) error {
if b.next[kind] != number {
return errOutOrderInsertion
}
blob, err := rlp.EncodeToBytes(item)
if err != nil {
return err
}
b.data[kind] = append(b.data[kind], blob)
b.next[kind]++
b.size[kind] += int64(len(blob))
return nil
}
// AppendRaw adds an item without RLP-encoding it.
func (b *memoryBatch) AppendRaw(kind string, number uint64, blob []byte) error {
if b.next[kind] != number {
return errOutOrderInsertion
}
b.data[kind] = append(b.data[kind], common.CopyBytes(blob))
b.next[kind]++
b.size[kind] += int64(len(blob))
return nil
}
// commit is called at the end of a write operation and writes all remaining
// data to tables.
func (b *memoryBatch) commit(freezer *MemoryFreezer) (items uint64, writeSize int64, err error) {
// Check that count agrees on all batches.
items = math.MaxUint64
for name, next := range b.next {
if items < math.MaxUint64 && next != items {
return 0, 0, fmt.Errorf("table %s is at item %d, want %d", name, next, items)
}
items = next
}
// Commit all table batches.
for name, batch := range b.data {
table := freezer.tables[name]
if err := table.commit(batch); err != nil {
return 0, 0, err
}
writeSize += b.size[name]
}
return items, writeSize, nil
}
// MemoryFreezer is an ephemeral ancient store. It implements the ethdb.AncientStore
// interface and can be used along with ephemeral key-value store.
type MemoryFreezer struct {
items uint64 // Number of items stored
tail uint64 // Number of the first stored item in the freezer
readonly bool // Flag if the freezer is only for reading
lock sync.RWMutex // Lock to protect fields
tables map[string]*memoryTable // Tables for storing everything
writeBatch *memoryBatch // Pre-allocated write batch
}
// NewMemoryFreezer initializes an in-memory freezer instance.
func NewMemoryFreezer(readonly bool, tableName map[string]bool) *MemoryFreezer {
tables := make(map[string]*memoryTable)
for name := range tableName {
tables[name] = newMemoryTable(name)
}
return &MemoryFreezer{
writeBatch: newMemoryBatch(),
readonly: readonly,
tables: tables,
}
}
// HasAncient returns an indicator whether the specified data exists.
func (f *MemoryFreezer) HasAncient(kind string, number uint64) (bool, error) {
f.lock.RLock()
defer f.lock.RUnlock()
if table := f.tables[kind]; table != nil {
return table.has(number), nil
}
return false, nil
}
// Ancient retrieves an ancient binary blob from the in-memory freezer.
func (f *MemoryFreezer) Ancient(kind string, number uint64) ([]byte, error) {
f.lock.RLock()
defer f.lock.RUnlock()
t := f.tables[kind]
if t == nil {
return nil, errUnknownTable
}
data, err := t.retrieve(number, 1, 0)
if err != nil {
return nil, err
}
return data[0], nil
}
// AncientRange retrieves multiple items in sequence, starting from the index 'start'.
// It will return
// - at most 'count' items,
// - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize),
// but will otherwise return as many items as fit into maxByteSize.
// - if maxBytes is not specified, 'count' items will be returned if they are present
func (f *MemoryFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
f.lock.RLock()
defer f.lock.RUnlock()
t := f.tables[kind]
if t == nil {
return nil, errUnknownTable
}
return t.retrieve(start, count, maxBytes)
}
// Ancients returns the ancient item numbers in the freezer.
func (f *MemoryFreezer) Ancients() (uint64, error) {
f.lock.RLock()
defer f.lock.RUnlock()
return f.items, nil
}
// Tail returns the number of first stored item in the freezer.
// This number can also be interpreted as the total deleted item numbers.
func (f *MemoryFreezer) Tail() (uint64, error) {
f.lock.RLock()
defer f.lock.RUnlock()
return f.tail, nil
}
// AncientSize returns the ancient size of the specified category.
func (f *MemoryFreezer) AncientSize(kind string) (uint64, error) {
f.lock.RLock()
defer f.lock.RUnlock()
if table := f.tables[kind]; table != nil {
return table.size, nil
}
return 0, errUnknownTable
}
// ReadAncients runs the given read operation while ensuring that no writes take place
// on the underlying freezer.
func (f *MemoryFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) {
f.lock.RLock()
defer f.lock.RUnlock()
return fn(f)
}
// ModifyAncients runs the given write operation.
func (f *MemoryFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) {
f.lock.Lock()
defer f.lock.Unlock()
if f.readonly {
return 0, errReadOnly
}
// Roll back all tables to the starting position in case of error.
defer func(old uint64) {
if err == nil {
return
}
// The write operation has failed. Go back to the previous item position.
for name, table := range f.tables {
err := table.truncateHead(old)
if err != nil {
log.Error("Freezer table roll-back failed", "table", name, "index", old, "err", err)
}
}
}(f.items)
// Modify the ancients in batch.
f.writeBatch.reset(f)
if err := fn(f.writeBatch); err != nil {
return 0, err
}
item, writeSize, err := f.writeBatch.commit(f)
if err != nil {
return 0, err
}
f.items = item
return writeSize, nil
}
// TruncateHead discards any recent data above the provided threshold number.
// It returns the previous head number.
func (f *MemoryFreezer) TruncateHead(items uint64) (uint64, error) {
f.lock.Lock()
defer f.lock.Unlock()
if f.readonly {
return 0, errReadOnly
}
old := f.items
if old <= items {
return old, nil
}
for _, table := range f.tables {
if err := table.truncateHead(items); err != nil {
return 0, err
}
}
f.items = items
return old, nil
}
// TruncateTail discards any recent data below the provided threshold number.
func (f *MemoryFreezer) TruncateTail(tail uint64) (uint64, error) {
f.lock.Lock()
defer f.lock.Unlock()
if f.readonly {
return 0, errReadOnly
}
old := f.tail
if old >= tail {
return old, nil
}
for _, table := range f.tables {
if err := table.truncateTail(tail); err != nil {
return 0, err
}
}
f.tail = tail
return old, nil
}
// Sync flushes all data tables to disk.
func (f *MemoryFreezer) Sync() error {
return nil
}
// MigrateTable processes and migrates entries of a given table to a new format.
// The second argument is a function that takes a raw entry and returns it
// in the newest format.
func (f *MemoryFreezer) MigrateTable(string, func([]byte) ([]byte, error)) error {
return errors.New("not implemented")
}
// Close releases all the sources held by the memory freezer. It will panic if
// any following invocation is made to a closed freezer.
func (f *MemoryFreezer) Close() error {
f.lock.Lock()
defer f.lock.Unlock()
f.tables = nil
f.writeBatch = nil
return nil
}
// Reset drops all the data cached in the memory freezer and reset itself
// back to default state.
func (f *MemoryFreezer) Reset() error {
f.lock.Lock()
defer f.lock.Unlock()
tables := make(map[string]*memoryTable)
for name := range f.tables {
tables[name] = newMemoryTable(name)
}
f.tables = tables
f.items, f.tail = 0, 0
return nil
}

View File

@@ -0,0 +1,41 @@
// 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 rawdb
import (
"testing"
"github.com/ethereum/go-ethereum/core/rawdb/ancienttest"
"github.com/ethereum/go-ethereum/ethdb"
)
func TestMemoryFreezer(t *testing.T) {
ancienttest.TestAncientSuite(t, func(kinds []string) ethdb.AncientStore {
tables := make(map[string]bool)
for _, kind := range kinds {
tables[kind] = true
}
return NewMemoryFreezer(false, tables)
})
ancienttest.TestResettableAncientSuite(t, func(kinds []string) ethdb.ResettableAncientStore {
tables := make(map[string]bool)
for _, kind := range kinds {
tables[kind] = true
}
return NewMemoryFreezer(false, tables)
})
}

View File

@@ -30,16 +30,16 @@ const tmpSuffix = ".tmp"
// freezerOpenFunc is the function used to open/create a freezer. // freezerOpenFunc is the function used to open/create a freezer.
type freezerOpenFunc = func() (*Freezer, error) type freezerOpenFunc = func() (*Freezer, error)
// ResettableFreezer is a wrapper of the freezer which makes the // resettableFreezer is a wrapper of the freezer which makes the
// freezer resettable. // freezer resettable.
type ResettableFreezer struct { type resettableFreezer struct {
freezer *Freezer freezer *Freezer
opener freezerOpenFunc opener freezerOpenFunc
datadir string datadir string
lock sync.RWMutex lock sync.RWMutex
} }
// NewResettableFreezer creates a resettable freezer, note freezer is // newResettableFreezer creates a resettable freezer, note freezer is
// only resettable if the passed file directory is exclusively occupied // only resettable if the passed file directory is exclusively occupied
// by the freezer. And also the user-configurable ancient root directory // by the freezer. And also the user-configurable ancient root directory
// is **not** supported for reset since it might be a mount and rename // is **not** supported for reset since it might be a mount and rename
@@ -48,7 +48,7 @@ type ResettableFreezer struct {
// //
// The reset function will delete directory atomically and re-create the // The reset function will delete directory atomically and re-create the
// freezer from scratch. // freezer from scratch.
func NewResettableFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*ResettableFreezer, error) { func newResettableFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*resettableFreezer, error) {
if err := cleanup(datadir); err != nil { if err := cleanup(datadir); err != nil {
return nil, err return nil, err
} }
@@ -59,7 +59,7 @@ func NewResettableFreezer(datadir string, namespace string, readonly bool, maxTa
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &ResettableFreezer{ return &resettableFreezer{
freezer: freezer, freezer: freezer,
opener: opener, opener: opener,
datadir: datadir, datadir: datadir,
@@ -70,7 +70,7 @@ func NewResettableFreezer(datadir string, namespace string, readonly bool, maxTa
// recreate the freezer from scratch. The atomicity of directory deletion // recreate the freezer from scratch. The atomicity of directory deletion
// is guaranteed by the rename operation, the leftover directory will be // is guaranteed by the rename operation, the leftover directory will be
// cleaned up in next startup in case crash happens after rename. // cleaned up in next startup in case crash happens after rename.
func (f *ResettableFreezer) Reset() error { func (f *resettableFreezer) Reset() error {
f.lock.Lock() f.lock.Lock()
defer f.lock.Unlock() defer f.lock.Unlock()
@@ -93,7 +93,7 @@ func (f *ResettableFreezer) Reset() error {
} }
// Close terminates the chain freezer, unmapping all the data files. // Close terminates the chain freezer, unmapping all the data files.
func (f *ResettableFreezer) Close() error { func (f *resettableFreezer) Close() error {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@@ -102,7 +102,7 @@ func (f *ResettableFreezer) Close() error {
// HasAncient returns an indicator whether the specified ancient data exists // HasAncient returns an indicator whether the specified ancient data exists
// in the freezer // in the freezer
func (f *ResettableFreezer) HasAncient(kind string, number uint64) (bool, error) { func (f *resettableFreezer) HasAncient(kind string, number uint64) (bool, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@@ -110,7 +110,7 @@ func (f *ResettableFreezer) HasAncient(kind string, number uint64) (bool, error)
} }
// Ancient retrieves an ancient binary blob from the append-only immutable files. // Ancient retrieves an ancient binary blob from the append-only immutable files.
func (f *ResettableFreezer) Ancient(kind string, number uint64) ([]byte, error) { func (f *resettableFreezer) Ancient(kind string, number uint64) ([]byte, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@@ -123,7 +123,7 @@ func (f *ResettableFreezer) Ancient(kind string, number uint64) ([]byte, error)
// - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize), // - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize),
// but will otherwise return as many items as fit into maxByteSize. // but will otherwise return as many items as fit into maxByteSize.
// - if maxBytes is not specified, 'count' items will be returned if they are present. // - if maxBytes is not specified, 'count' items will be returned if they are present.
func (f *ResettableFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { func (f *resettableFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@@ -131,7 +131,7 @@ func (f *ResettableFreezer) AncientRange(kind string, start, count, maxBytes uin
} }
// Ancients returns the length of the frozen items. // Ancients returns the length of the frozen items.
func (f *ResettableFreezer) Ancients() (uint64, error) { func (f *resettableFreezer) Ancients() (uint64, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@@ -139,7 +139,7 @@ func (f *ResettableFreezer) Ancients() (uint64, error) {
} }
// Tail returns the number of first stored item in the freezer. // Tail returns the number of first stored item in the freezer.
func (f *ResettableFreezer) Tail() (uint64, error) { func (f *resettableFreezer) Tail() (uint64, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@@ -147,7 +147,7 @@ func (f *ResettableFreezer) Tail() (uint64, error) {
} }
// AncientSize returns the ancient size of the specified category. // AncientSize returns the ancient size of the specified category.
func (f *ResettableFreezer) AncientSize(kind string) (uint64, error) { func (f *resettableFreezer) AncientSize(kind string) (uint64, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@@ -156,7 +156,7 @@ func (f *ResettableFreezer) AncientSize(kind string) (uint64, error) {
// ReadAncients runs the given read operation while ensuring that no writes take place // ReadAncients runs the given read operation while ensuring that no writes take place
// on the underlying freezer. // on the underlying freezer.
func (f *ResettableFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) { func (f *resettableFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@@ -164,7 +164,7 @@ func (f *ResettableFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (
} }
// ModifyAncients runs the given write operation. // ModifyAncients runs the given write operation.
func (f *ResettableFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) { func (f *resettableFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@@ -173,7 +173,7 @@ func (f *ResettableFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error)
// TruncateHead discards any recent data above the provided threshold number. // TruncateHead discards any recent data above the provided threshold number.
// It returns the previous head number. // It returns the previous head number.
func (f *ResettableFreezer) TruncateHead(items uint64) (uint64, error) { func (f *resettableFreezer) TruncateHead(items uint64) (uint64, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@@ -182,7 +182,7 @@ func (f *ResettableFreezer) TruncateHead(items uint64) (uint64, error) {
// TruncateTail discards any recent data below the provided threshold number. // TruncateTail discards any recent data below the provided threshold number.
// It returns the previous value // It returns the previous value
func (f *ResettableFreezer) TruncateTail(tail uint64) (uint64, error) { func (f *resettableFreezer) TruncateTail(tail uint64) (uint64, error) {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@@ -190,7 +190,7 @@ func (f *ResettableFreezer) TruncateTail(tail uint64) (uint64, error) {
} }
// Sync flushes all data tables to disk. // Sync flushes all data tables to disk.
func (f *ResettableFreezer) Sync() error { func (f *resettableFreezer) Sync() error {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()
@@ -199,7 +199,7 @@ func (f *ResettableFreezer) Sync() error {
// MigrateTable processes the entries in a given table in sequence // MigrateTable processes the entries in a given table in sequence
// converting them to a new format if they're of an old format. // converting them to a new format if they're of an old format.
func (f *ResettableFreezer) MigrateTable(kind string, convert convertLegacyFn) error { func (f *resettableFreezer) MigrateTable(kind string, convert convertLegacyFn) error {
f.lock.RLock() f.lock.RLock()
defer f.lock.RUnlock() defer f.lock.RUnlock()

View File

@@ -33,7 +33,7 @@ func TestResetFreezer(t *testing.T) {
{1, bytes.Repeat([]byte{1}, 2048)}, {1, bytes.Repeat([]byte{1}, 2048)},
{2, bytes.Repeat([]byte{2}, 2048)}, {2, bytes.Repeat([]byte{2}, 2048)},
} }
f, _ := NewResettableFreezer(t.TempDir(), "", false, 2048, freezerTestTableDef) f, _ := newResettableFreezer(t.TempDir(), "", false, 2048, freezerTestTableDef)
defer f.Close() defer f.Close()
f.ModifyAncients(func(op ethdb.AncientWriteOp) error { f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
@@ -87,7 +87,7 @@ func TestFreezerCleanup(t *testing.T) {
{2, bytes.Repeat([]byte{2}, 2048)}, {2, bytes.Repeat([]byte{2}, 2048)},
} }
datadir := t.TempDir() datadir := t.TempDir()
f, _ := NewResettableFreezer(datadir, "", false, 2048, freezerTestTableDef) f, _ := newResettableFreezer(datadir, "", false, 2048, freezerTestTableDef)
f.ModifyAncients(func(op ethdb.AncientWriteOp) error { f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for _, item := range items { for _, item := range items {
op.AppendRaw("test", item.id, item.blob) op.AppendRaw("test", item.id, item.blob)
@@ -98,7 +98,7 @@ func TestFreezerCleanup(t *testing.T) {
os.Rename(datadir, tmpName(datadir)) os.Rename(datadir, tmpName(datadir))
// Open the freezer again, trigger cleanup operation // Open the freezer again, trigger cleanup operation
f, _ = NewResettableFreezer(datadir, "", false, 2048, freezerTestTableDef) f, _ = newResettableFreezer(datadir, "", false, 2048, freezerTestTableDef)
f.Close() f.Close()
if _, err := os.Lstat(tmpName(datadir)); !os.IsNotExist(err) { if _, err := os.Lstat(tmpName(datadir)); !os.IsNotExist(err) {

View File

@@ -27,6 +27,7 @@ import (
"sync" "sync"
"testing" "testing"
"github.com/ethereum/go-ethereum/core/rawdb/ancienttest"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -480,3 +481,22 @@ func TestFreezerCloseSync(t *testing.T) {
t.Fatalf("want %v, have %v", have, want) t.Fatalf("want %v, have %v", have, want)
} }
} }
func TestFreezerSuite(t *testing.T) {
ancienttest.TestAncientSuite(t, func(kinds []string) ethdb.AncientStore {
tables := make(map[string]bool)
for _, kind := range kinds {
tables[kind] = true
}
f, _ := newFreezerForTesting(t, tables)
return f
})
ancienttest.TestResettableAncientSuite(t, func(kinds []string) ethdb.ResettableAncientStore {
tables := make(map[string]bool)
for _, kind := range kinds {
tables[kind] = true
}
f, _ := newResettableFreezer(t.TempDir(), "", false, 2048, tables)
return f
})
}

View File

@@ -17,7 +17,10 @@
package state package state
import ( import (
"fmt"
"maps" "maps"
"slices"
"strings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
@@ -130,3 +133,35 @@ func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) {
func (al *accessList) DeleteAddress(address common.Address) { func (al *accessList) DeleteAddress(address common.Address) {
delete(al.addresses, address) delete(al.addresses, address)
} }
// Equal returns true if the two access lists are identical
func (al *accessList) Equal(other *accessList) bool {
if !maps.Equal(al.addresses, other.addresses) {
return false
}
return slices.EqualFunc(al.slots, other.slots,
func(m map[common.Hash]struct{}, m2 map[common.Hash]struct{}) bool {
return maps.Equal(m, m2)
})
}
// PrettyPrint prints the contents of the access list in a human-readable form
func (al *accessList) PrettyPrint() string {
out := new(strings.Builder)
var sortedAddrs []common.Address
for addr := range al.addresses {
sortedAddrs = append(sortedAddrs, addr)
}
slices.SortFunc(sortedAddrs, common.Address.Cmp)
for _, addr := range sortedAddrs {
idx := al.addresses[addr]
fmt.Fprintf(out, "%#x : (idx %d)\n", addr, idx)
if idx >= 0 {
slotmap := al.slots[idx]
for h := range slotmap {
fmt.Fprintf(out, " %#x\n", h)
}
}
}
return out.String()
}

View File

@@ -17,6 +17,8 @@
package state package state
import ( import (
"maps"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@@ -29,6 +31,9 @@ type journalEntry interface {
// dirtied returns the Ethereum address modified by this journal entry. // dirtied returns the Ethereum address modified by this journal entry.
dirtied() *common.Address dirtied() *common.Address
// copy returns a deep-copied journal entry.
copy() journalEntry
} }
// journal contains the list of state modifications applied since the last state // journal contains the list of state modifications applied since the last state
@@ -83,22 +88,31 @@ func (j *journal) length() int {
return len(j.entries) return len(j.entries)
} }
// copy returns a deep-copied journal.
func (j *journal) copy() *journal {
entries := make([]journalEntry, 0, j.length())
for i := 0; i < j.length(); i++ {
entries = append(entries, j.entries[i].copy())
}
return &journal{
entries: entries,
dirties: maps.Clone(j.dirties),
}
}
type ( type (
// Changes to the account trie. // Changes to the account trie.
createObjectChange struct { createObjectChange struct {
account *common.Address account *common.Address
} }
resetObjectChange struct {
account *common.Address
prev *stateObject
prevdestruct bool
prevAccount []byte
prevStorage map[common.Hash][]byte
prevAccountOriginExist bool // createContractChange represents an account becoming a contract-account.
prevAccountOrigin []byte // This event happens prior to executing initcode. The journal-event simply
prevStorageOrigin map[common.Hash][]byte // manages the created-flag, in order to allow same-tx destruction.
createContractChange struct {
account common.Address
} }
selfDestructChange struct { selfDestructChange struct {
account *common.Address account *common.Address
prev bool // whether account had already self-destructed prev bool // whether account had already self-destructed
@@ -115,8 +129,9 @@ type (
prev uint64 prev uint64
} }
storageChange struct { storageChange struct {
account *common.Address account *common.Address
key, prevalue common.Hash key common.Hash
prevvalue *common.Hash
} }
codeChange struct { codeChange struct {
account *common.Address account *common.Address
@@ -136,6 +151,7 @@ type (
touchChange struct { touchChange struct {
account *common.Address account *common.Address
} }
// Changes to the access list // Changes to the access list
accessListAddAccountChange struct { accessListAddAccountChange struct {
address *common.Address address *common.Address
@@ -145,6 +161,7 @@ type (
slot *common.Hash slot *common.Hash
} }
// Changes to transient storage
transientStorageChange struct { transientStorageChange struct {
account *common.Address account *common.Address
key, prevalue common.Hash key, prevalue common.Hash
@@ -153,34 +170,30 @@ type (
func (ch createObjectChange) revert(s *StateDB) { func (ch createObjectChange) revert(s *StateDB) {
delete(s.stateObjects, *ch.account) delete(s.stateObjects, *ch.account)
delete(s.stateObjectsDirty, *ch.account)
} }
func (ch createObjectChange) dirtied() *common.Address { func (ch createObjectChange) dirtied() *common.Address {
return ch.account return ch.account
} }
func (ch resetObjectChange) revert(s *StateDB) { func (ch createObjectChange) copy() journalEntry {
s.setStateObject(ch.prev) return createObjectChange{
if !ch.prevdestruct { account: ch.account,
delete(s.stateObjectsDestruct, ch.prev.address)
}
if ch.prevAccount != nil {
s.accounts[ch.prev.addrHash] = ch.prevAccount
}
if ch.prevStorage != nil {
s.storages[ch.prev.addrHash] = ch.prevStorage
}
if ch.prevAccountOriginExist {
s.accountsOrigin[ch.prev.address] = ch.prevAccountOrigin
}
if ch.prevStorageOrigin != nil {
s.storagesOrigin[ch.prev.address] = ch.prevStorageOrigin
} }
} }
func (ch resetObjectChange) dirtied() *common.Address { func (ch createContractChange) revert(s *StateDB) {
return ch.account s.getStateObject(ch.account).newContract = false
}
func (ch createContractChange) dirtied() *common.Address {
return nil
}
func (ch createContractChange) copy() journalEntry {
return createContractChange{
account: ch.account,
}
} }
func (ch selfDestructChange) revert(s *StateDB) { func (ch selfDestructChange) revert(s *StateDB) {
@@ -195,6 +208,14 @@ func (ch selfDestructChange) dirtied() *common.Address {
return ch.account return ch.account
} }
func (ch selfDestructChange) copy() journalEntry {
return selfDestructChange{
account: ch.account,
prev: ch.prev,
prevbalance: new(uint256.Int).Set(ch.prevbalance),
}
}
var ripemd = common.HexToAddress("0000000000000000000000000000000000000003") var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
func (ch touchChange) revert(s *StateDB) { func (ch touchChange) revert(s *StateDB) {
@@ -204,6 +225,12 @@ func (ch touchChange) dirtied() *common.Address {
return ch.account return ch.account
} }
func (ch touchChange) copy() journalEntry {
return touchChange{
account: ch.account,
}
}
func (ch balanceChange) revert(s *StateDB) { func (ch balanceChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setBalance(ch.prev) s.getStateObject(*ch.account).setBalance(ch.prev)
} }
@@ -212,6 +239,13 @@ func (ch balanceChange) dirtied() *common.Address {
return ch.account return ch.account
} }
func (ch balanceChange) copy() journalEntry {
return balanceChange{
account: ch.account,
prev: new(uint256.Int).Set(ch.prev),
}
}
func (ch nonceChange) revert(s *StateDB) { func (ch nonceChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setNonce(ch.prev) s.getStateObject(*ch.account).setNonce(ch.prev)
} }
@@ -220,6 +254,13 @@ func (ch nonceChange) dirtied() *common.Address {
return ch.account return ch.account
} }
func (ch nonceChange) copy() journalEntry {
return nonceChange{
account: ch.account,
prev: ch.prev,
}
}
func (ch codeChange) revert(s *StateDB) { func (ch codeChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
} }
@@ -228,14 +269,30 @@ func (ch codeChange) dirtied() *common.Address {
return ch.account return ch.account
} }
func (ch codeChange) copy() journalEntry {
return codeChange{
account: ch.account,
prevhash: common.CopyBytes(ch.prevhash),
prevcode: common.CopyBytes(ch.prevcode),
}
}
func (ch storageChange) revert(s *StateDB) { func (ch storageChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue) s.getStateObject(*ch.account).setState(ch.key, ch.prevvalue)
} }
func (ch storageChange) dirtied() *common.Address { func (ch storageChange) dirtied() *common.Address {
return ch.account return ch.account
} }
func (ch storageChange) copy() journalEntry {
return storageChange{
account: ch.account,
key: ch.key,
prevvalue: ch.prevvalue,
}
}
func (ch transientStorageChange) revert(s *StateDB) { func (ch transientStorageChange) revert(s *StateDB) {
s.setTransientState(*ch.account, ch.key, ch.prevalue) s.setTransientState(*ch.account, ch.key, ch.prevalue)
} }
@@ -244,6 +301,14 @@ func (ch transientStorageChange) dirtied() *common.Address {
return nil return nil
} }
func (ch transientStorageChange) copy() journalEntry {
return transientStorageChange{
account: ch.account,
key: ch.key,
prevalue: ch.prevalue,
}
}
func (ch refundChange) revert(s *StateDB) { func (ch refundChange) revert(s *StateDB) {
s.refund = ch.prev s.refund = ch.prev
} }
@@ -252,6 +317,12 @@ func (ch refundChange) dirtied() *common.Address {
return nil return nil
} }
func (ch refundChange) copy() journalEntry {
return refundChange{
prev: ch.prev,
}
}
func (ch addLogChange) revert(s *StateDB) { func (ch addLogChange) revert(s *StateDB) {
logs := s.logs[ch.txhash] logs := s.logs[ch.txhash]
if len(logs) == 1 { if len(logs) == 1 {
@@ -266,6 +337,12 @@ func (ch addLogChange) dirtied() *common.Address {
return nil return nil
} }
func (ch addLogChange) copy() journalEntry {
return addLogChange{
txhash: ch.txhash,
}
}
func (ch addPreimageChange) revert(s *StateDB) { func (ch addPreimageChange) revert(s *StateDB) {
delete(s.preimages, ch.hash) delete(s.preimages, ch.hash)
} }
@@ -274,6 +351,12 @@ func (ch addPreimageChange) dirtied() *common.Address {
return nil return nil
} }
func (ch addPreimageChange) copy() journalEntry {
return addPreimageChange{
hash: ch.hash,
}
}
func (ch accessListAddAccountChange) revert(s *StateDB) { func (ch accessListAddAccountChange) revert(s *StateDB) {
/* /*
One important invariant here, is that whenever a (addr, slot) is added, if the One important invariant here, is that whenever a (addr, slot) is added, if the
@@ -291,6 +374,12 @@ func (ch accessListAddAccountChange) dirtied() *common.Address {
return nil return nil
} }
func (ch accessListAddAccountChange) copy() journalEntry {
return accessListAddAccountChange{
address: ch.address,
}
}
func (ch accessListAddSlotChange) revert(s *StateDB) { func (ch accessListAddSlotChange) revert(s *StateDB) {
s.accessList.DeleteSlot(*ch.address, *ch.slot) s.accessList.DeleteSlot(*ch.address, *ch.slot)
} }
@@ -298,3 +387,10 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
func (ch accessListAddSlotChange) dirtied() *common.Address { func (ch accessListAddSlotChange) dirtied() *common.Address {
return nil return nil
} }
func (ch accessListAddSlotChange) copy() journalEntry {
return accessListAddSlotChange{
address: ch.address,
slot: ch.slot,
}
}

View File

@@ -27,26 +27,14 @@ import (
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
type Code []byte
func (c Code) String() string {
return string(c) //strings.Join(Disassemble(c), " ")
}
type Storage map[common.Hash]common.Hash type Storage map[common.Hash]common.Hash
func (s Storage) String() (str string) {
for key, value := range s {
str += fmt.Sprintf("%X : %X\n", key, value)
}
return
}
func (s Storage) Copy() Storage { func (s Storage) Copy() Storage {
return maps.Clone(s) return maps.Clone(s)
} }
@@ -65,8 +53,8 @@ type stateObject struct {
data types.StateAccount // Account data with all mutations applied in the scope of block data types.StateAccount // Account data with all mutations applied in the scope of block
// Write caches. // Write caches.
trie Trie // storage trie, which becomes non-nil on first access trie Trie // storage trie, which becomes non-nil on first access
code Code // contract bytecode, which gets set when code is loaded code []byte // contract bytecode, which gets set when code is loaded
originStorage Storage // Storage cache of original entries to dedup rewrites originStorage Storage // Storage cache of original entries to dedup rewrites
pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
@@ -75,17 +63,16 @@ type stateObject struct {
// Cache flags. // Cache flags.
dirtyCode bool // true if the code was updated dirtyCode bool // true if the code was updated
// Flag whether the account was marked as self-destructed. The self-destructed account // Flag whether the account was marked as self-destructed. The self-destructed
// is still accessible in the scope of same transaction. // account is still accessible in the scope of same transaction.
selfDestructed bool selfDestructed bool
// Flag whether the account was marked as deleted. A self-destructed account // This is an EIP-6780 flag indicating whether the object is eligible for
// or an account that is considered as empty will be marked as deleted at // self-destruct according to EIP-6780. The flag could be set either when
// the end of transaction and no longer accessible anymore. // the contract is just created within the current transaction, or when the
deleted bool // object was previously existent and is being deployed as a contract within
// the current transaction.
// Flag whether the object was created in the current transaction newContract bool
created bool
} }
// empty returns whether the account is considered empty. // empty returns whether the account is considered empty.
@@ -95,10 +82,7 @@ func (s *stateObject) empty() bool {
// newObject creates a state object. // newObject creates a state object.
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject { func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject {
var ( origin := acct
origin = acct
created = acct == nil // true if the account was not existent
)
if acct == nil { if acct == nil {
acct = types.NewEmptyStateAccount() acct = types.NewEmptyStateAccount()
} }
@@ -111,7 +95,6 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
originStorage: make(Storage), originStorage: make(Storage),
pendingStorage: make(Storage), pendingStorage: make(Storage),
dirtyStorage: make(Storage), dirtyStorage: make(Storage),
created: created,
} }
} }
@@ -158,13 +141,20 @@ func (s *stateObject) getTrie() (Trie, error) {
// GetState retrieves a value from the account storage trie. // GetState retrieves a value from the account storage trie.
func (s *stateObject) GetState(key common.Hash) common.Hash { func (s *stateObject) GetState(key common.Hash) common.Hash {
value, _ := s.getState(key)
return value
}
// getState retrieves a value from the account storage trie and also returns if
// the slot is already dirty or not.
func (s *stateObject) getState(key common.Hash) (common.Hash, bool) {
// If we have a dirty value for this state entry, return it // If we have a dirty value for this state entry, return it
value, dirty := s.dirtyStorage[key] value, dirty := s.dirtyStorage[key]
if dirty { if dirty {
return value return value, true
} }
// Otherwise return the entry's original value // Otherwise return the entry's original value
return s.GetCommittedState(key) return s.GetCommittedState(key), false
} }
// GetCommittedState retrieves a value from the committed account storage trie. // GetCommittedState retrieves a value from the committed account storage trie.
@@ -227,25 +217,38 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
// SetState updates a value in account storage. // SetState updates a value in account storage.
func (s *stateObject) SetState(key, value common.Hash) { func (s *stateObject) SetState(key, value common.Hash) {
// If the new value is the same as old, don't set // If the new value is the same as old, don't set. Otherwise, track only the
prev := s.GetState(key) // dirty changes, supporting reverting all of it back to no change.
prev, dirty := s.getState(key)
if prev == value { if prev == value {
return return
} }
var prevvalue *common.Hash
if dirty {
prevvalue = &prev
}
// New value is different, update and journal the change // New value is different, update and journal the change
s.db.journal.append(storageChange{ s.db.journal.append(storageChange{
account: &s.address, account: &s.address,
key: key, key: key,
prevalue: prev, prevvalue: prevvalue,
}) })
if s.db.logger != nil && s.db.logger.OnStorageChange != nil { if s.db.logger != nil && s.db.logger.OnStorageChange != nil {
s.db.logger.OnStorageChange(s.address, key, prev, value) s.db.logger.OnStorageChange(s.address, key, prev, value)
} }
s.setState(key, value) s.setState(key, &value)
} }
func (s *stateObject) setState(key, value common.Hash) { // setState updates a value in account dirty storage. If the value being set is
s.dirtyStorage[key] = value // nil (assuming journal revert), the dirtyness is removed.
func (s *stateObject) setState(key common.Hash, value *common.Hash) {
// If the first set is being reverted, undo the dirty marker
if value == nil {
delete(s.dirtyStorage, key)
return
}
// Otherwise restore the previous value
s.dirtyStorage[key] = *value
} }
// finalise moves all dirty storage slots into the pending area to be hashed or // finalise moves all dirty storage slots into the pending area to be hashed or
@@ -253,9 +256,16 @@ func (s *stateObject) setState(key, value common.Hash) {
func (s *stateObject) finalise(prefetch bool) { func (s *stateObject) finalise(prefetch bool) {
slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage)) slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage))
for key, value := range s.dirtyStorage { for key, value := range s.dirtyStorage {
s.pendingStorage[key] = value // If the slot is different from its original value, move it into the
// pending area to be committed at the end of the block (and prefetch
// the pathways).
if value != s.originStorage[key] { if value != s.originStorage[key] {
s.pendingStorage[key] = value
slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure
} else {
// Otherwise, the slot was reverted to its original value, remove it
// from the pending area to avoid thrashing the data strutures.
delete(s.pendingStorage, key)
} }
} }
if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash { if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
@@ -264,6 +274,10 @@ func (s *stateObject) finalise(prefetch bool) {
if len(s.dirtyStorage) > 0 { if len(s.dirtyStorage) > 0 {
s.dirtyStorage = make(Storage) s.dirtyStorage = make(Storage)
} }
// Revoke the flag at the end of the transaction. It finalizes the status
// of the newly-created object as it's no longer eligible for self-destruct
// by EIP-6780. For non-newly-created objects, it's a no-op.
s.newContract = false
} }
// updateTrie is responsible for persisting cached storage changes into the // updateTrie is responsible for persisting cached storage changes into the
@@ -280,9 +294,6 @@ func (s *stateObject) updateTrie() (Trie, error) {
if len(s.pendingStorage) == 0 { if len(s.pendingStorage) == 0 {
return s.trie, nil return s.trie, nil
} }
// Track the amount of time wasted on updating the storage trie
defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now())
// The snapshot storage map for the object // The snapshot storage map for the object
var ( var (
storage map[common.Hash][]byte storage map[common.Hash][]byte
@@ -365,6 +376,11 @@ func (s *stateObject) updateTrie() (Trie, error) {
} }
s.db.StorageDeleted += 1 s.db.StorageDeleted += 1
} }
// If no slots were touched, issue a warning as we shouldn't have done all
// the above work in the first place
if len(usedStorage) == 0 {
log.Error("State object update was noop", "addr", s.address, "slots", len(s.pendingStorage))
}
if s.db.prefetcher != nil { if s.db.prefetcher != nil {
s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage) s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage)
} }
@@ -381,24 +397,21 @@ func (s *stateObject) updateRoot() {
if err != nil || tr == nil { if err != nil || tr == nil {
return return
} }
// Track the amount of time wasted on hashing the storage trie
defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now())
s.data.Root = tr.Hash() s.data.Root = tr.Hash()
} }
// commit obtains a set of dirty storage trie nodes and updates the account data. // commit obtains a set of dirty storage trie nodes and updates the account data.
// The returned set can be nil if nothing to commit. This function assumes all // The returned set can be nil if nothing to commit. This function assumes all
// storage mutations have already been flushed into trie by updateRoot. // storage mutations have already been flushed into trie by updateRoot.
//
// Note, commit may run concurrently across all the state objects. Do not assume
// thread-safe access to the statedb.
func (s *stateObject) commit() (*trienode.NodeSet, error) { func (s *stateObject) commit() (*trienode.NodeSet, error) {
// Short circuit if trie is not even loaded, don't bother with committing anything // Short circuit if trie is not even loaded, don't bother with committing anything
if s.trie == nil { if s.trie == nil {
s.origin = s.data.Copy() s.origin = s.data.Copy()
return nil, nil return nil, nil
} }
// Track the amount of time wasted on committing the storage trie
defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now())
// The trie is currently in an open state and could potentially contain // The trie is currently in an open state and could potentially contain
// cached mutations. Call commit to acquire a set of nodes that have been // cached mutations. Call commit to acquire a set of nodes that have been
// modified, the set can be nil if nothing to commit. // modified, the set can be nil if nothing to commit.
@@ -453,22 +466,22 @@ func (s *stateObject) setBalance(amount *uint256.Int) {
func (s *stateObject) deepCopy(db *StateDB) *stateObject { func (s *stateObject) deepCopy(db *StateDB) *stateObject {
obj := &stateObject{ obj := &stateObject{
db: db, db: db,
address: s.address, address: s.address,
addrHash: s.addrHash, addrHash: s.addrHash,
origin: s.origin, origin: s.origin,
data: s.data, data: s.data,
code: s.code,
originStorage: s.originStorage.Copy(),
pendingStorage: s.pendingStorage.Copy(),
dirtyStorage: s.dirtyStorage.Copy(),
dirtyCode: s.dirtyCode,
selfDestructed: s.selfDestructed,
newContract: s.newContract,
} }
if s.trie != nil { if s.trie != nil {
obj.trie = db.db.CopyTrie(s.trie) obj.trie = db.db.CopyTrie(s.trie)
} }
obj.code = s.code
obj.dirtyStorage = s.dirtyStorage.Copy()
obj.originStorage = s.originStorage.Copy()
obj.pendingStorage = s.pendingStorage.Copy()
obj.selfDestructed = s.selfDestructed
obj.dirtyCode = s.dirtyCode
obj.deleted = s.deleted
return obj return obj
} }
@@ -483,7 +496,7 @@ func (s *stateObject) Address() common.Address {
// Code returns the contract code associated with this object, if any. // Code returns the contract code associated with this object, if any.
func (s *stateObject) Code() []byte { func (s *stateObject) Code() []byte {
if s.code != nil { if len(s.code) != 0 {
return s.code return s.code
} }
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
@@ -501,7 +514,7 @@ func (s *stateObject) Code() []byte {
// or zero if none. This method is an almost mirror of Code, but uses a cache // or zero if none. This method is an almost mirror of Code, but uses a cache
// inside the database to avoid loading codes seen recently. // inside the database to avoid loading codes seen recently.
func (s *stateObject) CodeSize() int { func (s *stateObject) CodeSize() int {
if s.code != nil { if len(s.code) != 0 {
return len(s.code) return len(s.code)
} }
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {

View File

@@ -194,106 +194,20 @@ func TestSnapshotEmpty(t *testing.T) {
s.state.RevertToSnapshot(s.state.Snapshot()) s.state.RevertToSnapshot(s.state.Snapshot())
} }
func TestSnapshot2(t *testing.T) { func TestCreateObjectRevert(t *testing.T) {
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
addr := common.BytesToAddress([]byte("so0"))
snap := state.Snapshot()
stateobjaddr0 := common.BytesToAddress([]byte("so0")) state.CreateAccount(addr)
stateobjaddr1 := common.BytesToAddress([]byte("so1")) so0 := state.getStateObject(addr)
var storageaddr common.Hash
data0 := common.BytesToHash([]byte{17})
data1 := common.BytesToHash([]byte{18})
state.SetState(stateobjaddr0, storageaddr, data0)
state.SetState(stateobjaddr1, storageaddr, data1)
// db, trie are already non-empty values
so0 := state.getStateObject(stateobjaddr0)
so0.SetBalance(uint256.NewInt(42), tracing.BalanceChangeUnspecified) so0.SetBalance(uint256.NewInt(42), tracing.BalanceChangeUnspecified)
so0.SetNonce(43) so0.SetNonce(43)
so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'}) so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
so0.selfDestructed = false
so0.deleted = false
state.setStateObject(so0) state.setStateObject(so0)
root, _ := state.Commit(0, false) state.RevertToSnapshot(snap)
state, _ = New(root, state.db, state.snaps) if state.Exist(addr) {
t.Error("Unexpected account after revert")
// and one with deleted == true
so1 := state.getStateObject(stateobjaddr1)
so1.SetBalance(uint256.NewInt(52), tracing.BalanceChangeUnspecified)
so1.SetNonce(53)
so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'})
so1.selfDestructed = true
so1.deleted = true
state.setStateObject(so1)
so1 = state.getStateObject(stateobjaddr1)
if so1 != nil {
t.Fatalf("deleted object not nil when getting")
}
snapshot := state.Snapshot()
state.RevertToSnapshot(snapshot)
so0Restored := state.getStateObject(stateobjaddr0)
// Update lazily-loaded values before comparing.
so0Restored.GetState(storageaddr)
so0Restored.Code()
// non-deleted is equal (restored)
compareStateObjects(so0Restored, so0, t)
// deleted should be nil, both before and after restore of state copy
so1Restored := state.getStateObject(stateobjaddr1)
if so1Restored != nil {
t.Fatalf("deleted object not nil after restoring snapshot: %+v", so1Restored)
}
}
func compareStateObjects(so0, so1 *stateObject, t *testing.T) {
if so0.Address() != so1.Address() {
t.Fatalf("Address mismatch: have %v, want %v", so0.address, so1.address)
}
if so0.Balance().Cmp(so1.Balance()) != 0 {
t.Fatalf("Balance mismatch: have %v, want %v", so0.Balance(), so1.Balance())
}
if so0.Nonce() != so1.Nonce() {
t.Fatalf("Nonce mismatch: have %v, want %v", so0.Nonce(), so1.Nonce())
}
if so0.data.Root != so1.data.Root {
t.Errorf("Root mismatch: have %x, want %x", so0.data.Root[:], so1.data.Root[:])
}
if !bytes.Equal(so0.CodeHash(), so1.CodeHash()) {
t.Fatalf("CodeHash mismatch: have %v, want %v", so0.CodeHash(), so1.CodeHash())
}
if !bytes.Equal(so0.code, so1.code) {
t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code)
}
if len(so1.dirtyStorage) != len(so0.dirtyStorage) {
t.Errorf("Dirty storage size mismatch: have %d, want %d", len(so1.dirtyStorage), len(so0.dirtyStorage))
}
for k, v := range so1.dirtyStorage {
if so0.dirtyStorage[k] != v {
t.Errorf("Dirty storage key %x mismatch: have %v, want %v", k, so0.dirtyStorage[k], v)
}
}
for k, v := range so0.dirtyStorage {
if so1.dirtyStorage[k] != v {
t.Errorf("Dirty storage key %x mismatch: have %v, want none.", k, v)
}
}
if len(so1.originStorage) != len(so0.originStorage) {
t.Errorf("Origin storage size mismatch: have %d, want %d", len(so1.originStorage), len(so0.originStorage))
}
for k, v := range so1.originStorage {
if so0.originStorage[k] != v {
t.Errorf("Origin storage key %x mismatch: have %v, want %v", k, so0.originStorage[k], v)
}
}
for k, v := range so0.originStorage {
if so1.originStorage[k] != v {
t.Errorf("Origin storage key %x mismatch: have %v, want none.", k, v)
}
} }
} }

View File

@@ -23,6 +23,7 @@ import (
"math/big" "math/big"
"slices" "slices"
"sort" "sort"
"sync"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@@ -37,13 +38,37 @@ import (
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/triestate" "github.com/ethereum/go-ethereum/trie/triestate"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"golang.org/x/sync/errgroup"
) )
// TriesInMemory represents the number of layers that are kept in RAM.
const TriesInMemory = 128
type revision struct { type revision struct {
id int id int
journalIndex int journalIndex int
} }
type mutationType int
const (
update mutationType = iota
deletion
)
type mutation struct {
typ mutationType
applied bool
}
func (m *mutation) copy() *mutation {
return &mutation{typ: m.typ, applied: m.applied}
}
func (m *mutation) isDelete() bool {
return m.typ == deletion
}
// StateDB structs within the ethereum protocol are used to store anything // StateDB structs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing // within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve: // nested states. It's the general query interface to retrieve:
@@ -75,12 +100,22 @@ type StateDB struct {
accountsOrigin map[common.Address][]byte // The original value of mutated accounts in 'slim RLP' encoding accountsOrigin map[common.Address][]byte // The original value of mutated accounts in 'slim RLP' encoding
storagesOrigin map[common.Address]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format storagesOrigin map[common.Address]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format
// This map holds 'live' objects, which will get modified while processing // This map holds 'live' objects, which will get modified while
// a state transition. // processing a state transition.
stateObjects map[common.Address]*stateObject stateObjects map[common.Address]*stateObject
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution // This map holds 'deleted' objects. An object with the same address
stateObjectsDestruct map[common.Address]*types.StateAccount // State objects destructed in the block along with its previous value // might also occur in the 'stateObjects' map due to account
// resurrection. The account value is tracked as the original value
// before the transition. This map is populated at the transaction
// boundaries.
stateObjectsDestruct map[common.Address]*types.StateAccount
// This map tracks the account mutations that occurred during the
// transition. Uncommitted mutations belonging to the same account
// can be merged into a single one which is equivalent from database's
// perspective. This map is populated at the transaction boundaries.
mutations map[common.Address]*mutation
// DB error. // DB error.
// State objects are used by the consensus core and VM which are // State objects are used by the consensus core and VM which are
@@ -121,7 +156,6 @@ type StateDB struct {
AccountUpdates time.Duration AccountUpdates time.Duration
AccountCommits time.Duration AccountCommits time.Duration
StorageReads time.Duration StorageReads time.Duration
StorageHashes time.Duration
StorageUpdates time.Duration StorageUpdates time.Duration
StorageCommits time.Duration StorageCommits time.Duration
SnapshotAccountReads time.Duration SnapshotAccountReads time.Duration
@@ -154,9 +188,8 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
accountsOrigin: make(map[common.Address][]byte), accountsOrigin: make(map[common.Address][]byte),
storagesOrigin: make(map[common.Address]map[common.Hash][]byte), storagesOrigin: make(map[common.Address]map[common.Hash][]byte),
stateObjects: make(map[common.Address]*stateObject), stateObjects: make(map[common.Address]*stateObject),
stateObjectsPending: make(map[common.Address]struct{}),
stateObjectsDirty: make(map[common.Address]struct{}),
stateObjectsDestruct: make(map[common.Address]*types.StateAccount), stateObjectsDestruct: make(map[common.Address]*types.StateAccount),
mutations: make(map[common.Address]*mutation),
logs: make(map[common.Hash][]*types.Log), logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte), preimages: make(map[common.Hash][]byte),
journal: newJournal(), journal: newJournal(),
@@ -472,8 +505,7 @@ func (s *StateDB) Selfdestruct6780(addr common.Address) {
if stateObject == nil { if stateObject == nil {
return return
} }
if stateObject.newContract {
if stateObject.created {
s.SelfDestruct(addr) s.SelfDestruct(addr)
} }
} }
@@ -552,24 +584,16 @@ func (s *StateDB) deleteStateObject(addr common.Address) {
} }
// getStateObject retrieves a state object given by the address, returning nil if // getStateObject retrieves a state object given by the address, returning nil if
// the object is not found or was deleted in this execution context. If you need // the object is not found or was deleted in this execution context.
// to differentiate between non-existent/just-deleted, use getDeletedStateObject.
func (s *StateDB) getStateObject(addr common.Address) *stateObject { func (s *StateDB) getStateObject(addr common.Address) *stateObject {
if obj := s.getDeletedStateObject(addr); obj != nil && !obj.deleted {
return obj
}
return nil
}
// getDeletedStateObject is similar to getStateObject, but instead of returning
// nil for a deleted state object, it returns the actual object with the deleted
// flag set. This is needed by the state journal to revert to the correct s-
// destructed object instead of wiping all knowledge about the state object.
func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
// Prefer live objects if any is available // Prefer live objects if any is available
if obj := s.stateObjects[addr]; obj != nil { if obj := s.stateObjects[addr]; obj != nil {
return obj return obj
} }
// Short circuit if the account is already destructed in this block.
if _, ok := s.stateObjectsDestruct[addr]; ok {
return nil
}
// If no live objects are available, attempt to use snapshots // If no live objects are available, attempt to use snapshots
var data *types.StateAccount var data *types.StateAccount
if s.snap != nil { if s.snap != nil {
@@ -622,69 +646,40 @@ func (s *StateDB) setStateObject(object *stateObject) {
// getOrNewStateObject retrieves a state object or create a new state object if nil. // getOrNewStateObject retrieves a state object or create a new state object if nil.
func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject { func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject {
stateObject := s.getStateObject(addr) obj := s.getStateObject(addr)
if stateObject == nil { if obj == nil {
stateObject, _ = s.createObject(addr) obj = s.createObject(addr)
} }
return stateObject return obj
} }
// createObject creates a new state object. If there is an existing account with // createObject creates a new state object. The assumption is held there is no
// the given address, it is overwritten and returned as the second return value. // existing account with the given address, otherwise it will be silently overwritten.
func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) { func (s *StateDB) createObject(addr common.Address) *stateObject {
prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that! obj := newObject(s, addr, nil)
newobj = newObject(s, addr, nil) s.journal.append(createObjectChange{account: &addr})
if prev == nil { s.setStateObject(obj)
s.journal.append(createObjectChange{account: &addr}) return obj
} else {
// The original account should be marked as destructed and all cached
// account and storage data should be cleared as well. Note, it must
// be done here, otherwise the destruction event of "original account"
// will be lost.
_, prevdestruct := s.stateObjectsDestruct[prev.address]
if !prevdestruct {
s.stateObjectsDestruct[prev.address] = prev.origin
}
// There may be some cached account/storage data already since IntermediateRoot
// will be called for each transaction before byzantium fork which will always
// cache the latest account/storage data.
prevAccount, ok := s.accountsOrigin[prev.address]
s.journal.append(resetObjectChange{
account: &addr,
prev: prev,
prevdestruct: prevdestruct,
prevAccount: s.accounts[prev.addrHash],
prevStorage: s.storages[prev.addrHash],
prevAccountOriginExist: ok,
prevAccountOrigin: prevAccount,
prevStorageOrigin: s.storagesOrigin[prev.address],
})
delete(s.accounts, prev.addrHash)
delete(s.storages, prev.addrHash)
delete(s.accountsOrigin, prev.address)
delete(s.storagesOrigin, prev.address)
}
s.setStateObject(newobj)
if prev != nil && !prev.deleted {
return newobj, prev
}
return newobj, nil
} }
// CreateAccount explicitly creates a state object. If a state object with the address // CreateAccount explicitly creates a new state object, assuming that the
// already exists the balance is carried over to the new account. // account did not previously exist in the state. If the account already
// // exists, this function will silently overwrite it which might lead to a
// CreateAccount is called during the EVM CREATE operation. The situation might arise that // consensus bug eventually.
// a contract does the following:
//
// 1. sends funds to sha(account ++ (nonce + 1))
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
//
// Carrying over the balance ensures that Ether doesn't disappear.
func (s *StateDB) CreateAccount(addr common.Address) { func (s *StateDB) CreateAccount(addr common.Address) {
newObj, prev := s.createObject(addr) s.createObject(addr)
if prev != nil { }
newObj.setBalance(prev.data.Balance)
// CreateContract is used whenever a contract is created. This may be preceded
// by CreateAccount, but that is not required if it already existed in the
// state due to funds sent beforehand.
// This operation sets the 'newContract'-flag, which is required in order to
// correctly handle EIP-6780 'delete-in-same-transaction' logic.
func (s *StateDB) CreateContract(addr common.Address) {
obj := s.getStateObject(addr)
if !obj.newContract {
obj.newContract = true
s.journal.append(createContractChange{account: addr})
} }
} }
@@ -695,21 +690,25 @@ func (s *StateDB) Copy() *StateDB {
state := &StateDB{ state := &StateDB{
db: s.db, db: s.db,
trie: s.db.CopyTrie(s.trie), trie: s.db.CopyTrie(s.trie),
hasher: crypto.NewKeccakState(),
originalRoot: s.originalRoot, originalRoot: s.originalRoot,
accounts: copySet(s.accounts), accounts: copySet(s.accounts),
storages: copy2DSet(s.storages), storages: copy2DSet(s.storages),
accountsOrigin: copySet(s.accountsOrigin), accountsOrigin: copySet(s.accountsOrigin),
storagesOrigin: copy2DSet(s.storagesOrigin), storagesOrigin: copy2DSet(s.storagesOrigin),
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
stateObjectsDestruct: maps.Clone(s.stateObjectsDestruct), stateObjectsDestruct: maps.Clone(s.stateObjectsDestruct),
mutations: make(map[common.Address]*mutation, len(s.mutations)),
dbErr: s.dbErr,
refund: s.refund, refund: s.refund,
thash: s.thash,
txIndex: s.txIndex,
logs: make(map[common.Hash][]*types.Log, len(s.logs)), logs: make(map[common.Hash][]*types.Log, len(s.logs)),
logSize: s.logSize, logSize: s.logSize,
preimages: maps.Clone(s.preimages), preimages: maps.Clone(s.preimages),
journal: newJournal(), journal: s.journal.copy(),
hasher: crypto.NewKeccakState(), validRevisions: slices.Clone(s.validRevisions),
nextRevisionId: s.nextRevisionId,
// In order for the block producer to be able to use and make additions // In order for the block producer to be able to use and make additions
// to the snapshot tree, we need to copy that as well. Otherwise, any // to the snapshot tree, we need to copy that as well. Otherwise, any
@@ -718,39 +717,14 @@ func (s *StateDB) Copy() *StateDB {
snaps: s.snaps, snaps: s.snaps,
snap: s.snap, snap: s.snap,
} }
// Copy the dirty states, logs, and preimages // Deep copy cached state objects.
for addr := range s.journal.dirties { for addr, obj := range s.stateObjects {
// As documented [here](https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527), state.stateObjects[addr] = obj.deepCopy(state)
// and in the Finalise-method, there is a case where an object is in the journal but not
// in the stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we need to check for
// nil
if object, exist := s.stateObjects[addr]; exist {
// Even though the original object is dirty, we are not copying the journal,
// so we need to make sure that any side-effect the journal would have caused
// during a commit (or similar op) is already applied to the copy.
state.stateObjects[addr] = object.deepCopy(state)
state.stateObjectsDirty[addr] = struct{}{} // Mark the copy dirty to force internal (code/state) commits
state.stateObjectsPending[addr] = struct{}{} // Mark the copy pending to force external (account) commits
}
} }
// Above, we don't copy the actual journal. This means that if the copy // Deep copy the object state markers.
// is copied, the loop above will be a no-op, since the copy's journal for addr, op := range s.mutations {
// is empty. Thus, here we iterate over stateObjects, to enable copies state.mutations[addr] = op.copy()
// of copies.
for addr := range s.stateObjectsPending {
if _, exist := state.stateObjects[addr]; !exist {
state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state)
}
state.stateObjectsPending[addr] = struct{}{}
} }
for addr := range s.stateObjectsDirty {
if _, exist := state.stateObjects[addr]; !exist {
state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state)
}
state.stateObjectsDirty[addr] = struct{}{}
}
// Deep copy the logs occurred in the scope of block // Deep copy the logs occurred in the scope of block
for hash, logs := range s.logs { for hash, logs := range s.logs {
cpy := make([]*types.Log, len(logs)) cpy := make([]*types.Log, len(logs))
@@ -760,7 +734,6 @@ func (s *StateDB) Copy() *StateDB {
} }
state.logs[hash] = cpy state.logs[hash] = cpy
} }
// Do we need to copy the access list and transient storage? // Do we need to copy the access list and transient storage?
// In practice: No. At the start of a transaction, these two lists are empty. // In practice: No. At the start of a transaction, these two lists are empty.
// In practice, we only ever copy state _between_ transactions/blocks, never // In practice, we only ever copy state _between_ transactions/blocks, never
@@ -825,7 +798,8 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
continue continue
} }
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
obj.deleted = true delete(s.stateObjects, obj.address)
s.markDelete(addr)
// If ether was sent to account post-selfdestruct it is burnt. // If ether was sent to account post-selfdestruct it is burnt.
if bal := obj.Balance(); s.logger != nil && s.logger.OnBalanceChange != nil && obj.selfDestructed && bal.Sign() != 0 { if bal := obj.Balance(); s.logger != nil && s.logger.OnBalanceChange != nil && obj.selfDestructed && bal.Sign() != 0 {
@@ -846,11 +820,8 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect)
} else { } else {
obj.finalise(true) // Prefetch slots in the background obj.finalise(true) // Prefetch slots in the background
s.markUpdate(addr)
} }
obj.created = false
s.stateObjectsPending[addr] = struct{}{}
s.stateObjectsDirty[addr] = struct{}{}
// At this point, also ship the address off to the precacher. The precacher // At this point, also ship the address off to the precacher. The precacher
// will start loading tries, and when the change is eventually committed, // will start loading tries, and when the change is eventually committed,
// the commit-phase will be a lot faster // the commit-phase will be a lot faster
@@ -889,11 +860,18 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// the account prefetcher. Instead, let's process all the storage updates // the account prefetcher. Instead, let's process all the storage updates
// first, giving the account prefetches just a few more milliseconds of time // first, giving the account prefetches just a few more milliseconds of time
// to pull useful data from disk. // to pull useful data from disk.
for addr := range s.stateObjectsPending { start := time.Now()
if obj := s.stateObjects[addr]; !obj.deleted { for addr, op := range s.mutations {
obj.updateRoot() if op.applied {
continue
} }
if op.isDelete() {
continue
}
s.stateObjects[addr].updateRoot()
} }
s.StorageUpdates += time.Since(start)
// Now we're about to start to write changes to the trie. The trie is so far // Now we're about to start to write changes to the trie. The trie is so far
// _untouched_. We can check with the prefetcher, if it can give us a trie // _untouched_. We can check with the prefetcher, if it can give us a trie
// which has the same root, but also has some content loaded into it. // which has the same root, but also has some content loaded into it.
@@ -902,7 +880,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
s.trie = trie s.trie = trie
} }
} }
usedAddrs := make([][]byte, 0, len(s.stateObjectsPending))
// Perform updates before deletions. This prevents resolution of unnecessary trie nodes // Perform updates before deletions. This prevents resolution of unnecessary trie nodes
// in circumstances similar to the following: // in circumstances similar to the following:
// //
@@ -913,13 +890,21 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// If the self-destruct is handled first, then `P` would be left with only one child, thus collapsed // If the self-destruct is handled first, then `P` would be left with only one child, thus collapsed
// into a shortnode. This requires `B` to be resolved from disk. // into a shortnode. This requires `B` to be resolved from disk.
// Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved. // Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved.
var deletedAddrs []common.Address var (
for addr := range s.stateObjectsPending { usedAddrs [][]byte
if obj := s.stateObjects[addr]; !obj.deleted { deletedAddrs []common.Address
s.updateStateObject(obj) )
s.AccountUpdated += 1 for addr, op := range s.mutations {
if op.applied {
continue
}
op.applied = true
if op.isDelete() {
deletedAddrs = append(deletedAddrs, addr)
} else { } else {
deletedAddrs = append(deletedAddrs, obj.address) s.updateStateObject(s.stateObjects[addr])
s.AccountUpdated += 1
} }
usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure
} }
@@ -930,9 +915,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
if prefetcher != nil { if prefetcher != nil {
prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs) prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs)
} }
if len(s.stateObjectsPending) > 0 {
s.stateObjectsPending = make(map[common.Address]struct{})
}
// Track the amount of time wasted on hashing the account trie // Track the amount of time wasted on hashing the account trie
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now()) defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())
@@ -1169,62 +1151,108 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
storageTrieNodesUpdated int storageTrieNodesUpdated int
storageTrieNodesDeleted int storageTrieNodesDeleted int
nodes = trienode.NewMergedNodeSet() nodes = trienode.NewMergedNodeSet()
codeWriter = s.db.DiskDB().NewBatch()
) )
// Handle all state deletions first // Handle all state deletions first
if err := s.handleDestruction(nodes); err != nil { if err := s.handleDestruction(nodes); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
// Handle all state updates afterwards // Handle all state updates afterwards, concurrently to one another to shave
for addr := range s.stateObjectsDirty { // off some milliseconds from the commit operation. Also accumulate the code
obj := s.stateObjects[addr] // writes to run in parallel with the computations.
if obj.deleted { start := time.Now()
var (
code = s.db.DiskDB().NewBatch()
lock sync.Mutex
root common.Hash
workers errgroup.Group
)
// Schedule the account trie first since that will be the biggest, so give
// it the most time to crunch.
//
// TODO(karalabe): This account trie commit is *very* heavy. 5-6ms at chain
// heads, which seems excessive given that it doesn't do hashing, it just
// shuffles some data. For comparison, the *hashing* at chain head is 2-3ms.
// We need to investigate what's happening as it seems something's wonky.
// Obviously it's not an end of the world issue, just something the original
// code didn't anticipate for.
workers.Go(func() error {
// Write the account trie changes, measuring the amount of wasted time
newroot, set, err := s.trie.Commit(true)
if err != nil {
return err
}
root = newroot
// Merge the dirty nodes of account trie into global set
lock.Lock()
defer lock.Unlock()
if set != nil {
if err = nodes.Merge(set); err != nil {
return err
}
accountTrieNodesUpdated, accountTrieNodesDeleted = set.Size()
}
s.AccountCommits = time.Since(start)
return nil
})
// Schedule each of the storage tries that need to be updated, so they can
// run concurrently to one another.
//
// TODO(karalabe): Experimentally, the account commit takes approximately the
// same time as all the storage commits combined, so we could maybe only have
// 2 threads in total. But that kind of depends on the account commit being
// more expensive than it should be, so let's fix that and revisit this todo.
for addr, op := range s.mutations {
if op.isDelete() {
continue continue
} }
// Write any contract code associated with the state object // Write any contract code associated with the state object
obj := s.stateObjects[addr]
if obj.code != nil && obj.dirtyCode { if obj.code != nil && obj.dirtyCode {
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code) rawdb.WriteCode(code, common.BytesToHash(obj.CodeHash()), obj.code)
obj.dirtyCode = false obj.dirtyCode = false
} }
// Write any storage changes in the state object to its storage trie // Run the storage updates concurrently to one another
set, err := obj.commit() workers.Go(func() error {
if err != nil { // Write any storage changes in the state object to its storage trie
return common.Hash{}, err set, err := obj.commit()
} if err != nil {
// Merge the dirty nodes of storage trie into global set. It is possible return err
// that the account was destructed and then resurrected in the same block.
// In this case, the node set is shared by both accounts.
if set != nil {
if err := nodes.Merge(set); err != nil {
return common.Hash{}, err
} }
updates, deleted := set.Size() // Merge the dirty nodes of storage trie into global set. It is possible
storageTrieNodesUpdated += updates // that the account was destructed and then resurrected in the same block.
storageTrieNodesDeleted += deleted // In this case, the node set is shared by both accounts.
} lock.Lock()
} defer lock.Unlock()
if codeWriter.ValueSize() > 0 {
if err := codeWriter.Write(); err != nil {
log.Crit("Failed to commit dirty codes", "error", err)
}
}
// Write the account trie changes, measuring the amount of wasted time
start := time.Now()
root, set, err := s.trie.Commit(true) if set != nil {
if err != nil { if err = nodes.Merge(set); err != nil {
return err
}
updates, deleted := set.Size()
storageTrieNodesUpdated += updates
storageTrieNodesDeleted += deleted
}
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
return nil
})
}
// Schedule the code commits to run concurrently too. This shouldn't really
// take much since we don't often commit code, but since it's disk access,
// it's always yolo.
workers.Go(func() error {
if code.ValueSize() > 0 {
if err := code.Write(); err != nil {
log.Crit("Failed to commit dirty codes", "error", err)
}
}
return nil
})
// Wait for everything to finish and update the metrics
if err := workers.Wait(); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
// Merge the dirty nodes of account trie into global set
if set != nil {
if err := nodes.Merge(set); err != nil {
return common.Hash{}, err
}
accountTrieNodesUpdated, accountTrieNodesDeleted = set.Size()
}
// Report the commit metrics
s.AccountCommits += time.Since(start)
accountUpdatedMeter.Mark(int64(s.AccountUpdated)) accountUpdatedMeter.Mark(int64(s.AccountUpdated))
storageUpdatedMeter.Mark(int64(s.StorageUpdated)) storageUpdatedMeter.Mark(int64(s.StorageUpdated))
accountDeletedMeter.Mark(int64(s.AccountDeleted)) accountDeletedMeter.Mark(int64(s.AccountDeleted))
@@ -1244,12 +1272,12 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
if err := s.snaps.Update(root, parent, s.convertAccountSet(s.stateObjectsDestruct), s.accounts, s.storages); err != nil { if err := s.snaps.Update(root, parent, s.convertAccountSet(s.stateObjectsDestruct), s.accounts, s.storages); err != nil {
log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err) log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err)
} }
// Keep 128 diff layers in the memory, persistent layer is 129th. // Keep TriesInMemory diff layers in the memory, persistent layer is 129th.
// - head layer is paired with HEAD state // - head layer is paired with HEAD state
// - head-1 layer is paired with HEAD-1 state // - head-1 layer is paired with HEAD-1 state
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state // - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
if err := s.snaps.Cap(root, 128); err != nil { if err := s.snaps.Cap(root, TriesInMemory); err != nil {
log.Warn("Failed to cap snapshot tree", "root", root, "layers", 128, "err", err) log.Warn("Failed to cap snapshot tree", "root", root, "layers", TriesInMemory, "err", err)
} }
} }
s.SnapshotCommits += time.Since(start) s.SnapshotCommits += time.Since(start)
@@ -1280,7 +1308,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
s.storages = make(map[common.Hash]map[common.Hash][]byte) s.storages = make(map[common.Hash]map[common.Hash][]byte)
s.accountsOrigin = make(map[common.Address][]byte) s.accountsOrigin = make(map[common.Address][]byte)
s.storagesOrigin = make(map[common.Address]map[common.Hash][]byte) s.storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
s.stateObjectsDirty = make(map[common.Address]struct{}) s.mutations = make(map[common.Address]*mutation)
s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount) s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount)
return root, nil return root, nil
} }
@@ -1395,3 +1423,19 @@ func copy2DSet[k comparable](set map[k]map[common.Hash][]byte) map[k]map[common.
} }
return copied return copied
} }
func (s *StateDB) markDelete(addr common.Address) {
if _, ok := s.mutations[addr]; !ok {
s.mutations[addr] = &mutation{}
}
s.mutations[addr].applied = false
s.mutations[addr].typ = deletion
}
func (s *StateDB) markUpdate(addr common.Address) {
if _, ok := s.mutations[addr]; !ok {
s.mutations[addr] = &mutation{}
}
s.mutations[addr].applied = false
s.mutations[addr].typ = update
}

View File

@@ -96,7 +96,9 @@ func newStateTestAction(addr common.Address, r *rand.Rand, index int) testAction
{ {
name: "CreateAccount", name: "CreateAccount",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {
s.CreateAccount(addr) if !s.Exist(addr) {
s.CreateAccount(addr)
}
}, },
}, },
{ {

View File

@@ -21,9 +21,11 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"maps"
"math" "math"
"math/rand" "math/rand"
"reflect" "reflect"
"slices"
"strings" "strings"
"sync" "sync"
"testing" "testing"
@@ -225,6 +227,78 @@ func TestCopy(t *testing.T) {
} }
} }
// TestCopyWithDirtyJournal tests if Copy can correct create a equal copied
// stateDB with dirty journal present.
func TestCopyWithDirtyJournal(t *testing.T) {
db := NewDatabase(rawdb.NewMemoryDatabase())
orig, _ := New(types.EmptyRootHash, db, nil)
// Fill up the initial states
for i := byte(0); i < 255; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified)
obj.data.Root = common.HexToHash("0xdeadbeef")
orig.updateStateObject(obj)
}
root, _ := orig.Commit(0, true)
orig, _ = New(root, db, nil)
// modify all in memory without finalizing
for i := byte(0); i < 255; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.SubBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified)
orig.updateStateObject(obj)
}
cpy := orig.Copy()
orig.Finalise(true)
for i := byte(0); i < 255; i++ {
root := orig.GetStorageRoot(common.BytesToAddress([]byte{i}))
if root != (common.Hash{}) {
t.Errorf("Unexpected storage root %x", root)
}
}
cpy.Finalise(true)
for i := byte(0); i < 255; i++ {
root := cpy.GetStorageRoot(common.BytesToAddress([]byte{i}))
if root != (common.Hash{}) {
t.Errorf("Unexpected storage root %x", root)
}
}
if cpy.IntermediateRoot(true) != orig.IntermediateRoot(true) {
t.Error("State is not equal after copy")
}
}
// TestCopyObjectState creates an original state, S1, and makes a copy S2.
// It then proceeds to make changes to S1. Those changes are _not_ supposed
// to affect S2. This test checks that the copy properly deep-copies the objectstate
func TestCopyObjectState(t *testing.T) {
db := NewDatabase(rawdb.NewMemoryDatabase())
orig, _ := New(types.EmptyRootHash, db, nil)
// Fill up the initial states
for i := byte(0); i < 5; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified)
obj.data.Root = common.HexToHash("0xdeadbeef")
orig.updateStateObject(obj)
}
orig.Finalise(true)
cpy := orig.Copy()
for _, op := range cpy.mutations {
if have, want := op.applied, false; have != want {
t.Fatalf("Error in test itself, the 'done' flag should not be set before Commit, have %v want %v", have, want)
}
}
orig.Commit(0, true)
for _, op := range cpy.mutations {
if have, want := op.applied, false; have != want {
t.Fatalf("Error: original state affected copy, have %v want %v", have, want)
}
}
}
func TestSnapshotRandom(t *testing.T) { func TestSnapshotRandom(t *testing.T) {
config := &quick.Config{MaxCount: 1000} config := &quick.Config{MaxCount: 1000}
err := quick.Check((*snapshotTest).run, config) err := quick.Check((*snapshotTest).run, config)
@@ -308,7 +382,30 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
{ {
name: "CreateAccount", name: "CreateAccount",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {
s.CreateAccount(addr) if !s.Exist(addr) {
s.CreateAccount(addr)
}
},
},
{
name: "CreateContract",
fn: func(a testAction, s *StateDB) {
if !s.Exist(addr) {
s.CreateAccount(addr)
}
contractHash := s.GetCodeHash(addr)
emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash
storageRoot := s.GetStorageRoot(addr)
emptyStorage := storageRoot == (common.Hash{}) || storageRoot == types.EmptyRootHash
if s.GetNonce(addr) == 0 && emptyCode && emptyStorage {
s.CreateContract(addr)
// We also set some code here, to prevent the
// CreateContract action from being performed twice in a row,
// which would cause a difference in state when unrolling
// the journal. (CreateContact assumes created was false prior to
// invocation, and the journal rollback sets it to false).
s.SetCode(addr, []byte{1})
}
}, },
}, },
{ {
@@ -462,10 +559,14 @@ func forEachStorage(s *StateDB, addr common.Address, cb func(key, value common.H
if err != nil { if err != nil {
return err return err
} }
it := trie.NewIterator(trieIt) var (
it = trie.NewIterator(trieIt)
visited = make(map[common.Hash]bool)
)
for it.Next() { for it.Next() {
key := common.BytesToHash(s.trie.GetKey(it.Key)) key := common.BytesToHash(s.trie.GetKey(it.Key))
visited[key] = true
if value, dirty := so.dirtyStorage[key]; dirty { if value, dirty := so.dirtyStorage[key]; dirty {
if !cb(key, value) { if !cb(key, value) {
return nil return nil
@@ -505,6 +606,10 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
checkeq("GetCode", state.GetCode(addr), checkstate.GetCode(addr)) checkeq("GetCode", state.GetCode(addr), checkstate.GetCode(addr))
checkeq("GetCodeHash", state.GetCodeHash(addr), checkstate.GetCodeHash(addr)) checkeq("GetCodeHash", state.GetCodeHash(addr), checkstate.GetCodeHash(addr))
checkeq("GetCodeSize", state.GetCodeSize(addr), checkstate.GetCodeSize(addr)) checkeq("GetCodeSize", state.GetCodeSize(addr), checkstate.GetCodeSize(addr))
// Check newContract-flag
if obj := state.getStateObject(addr); obj != nil {
checkeq("IsNewContract", obj.newContract, checkstate.getStateObject(addr).newContract)
}
// Check storage. // Check storage.
if obj := state.getStateObject(addr); obj != nil { if obj := state.getStateObject(addr); obj != nil {
forEachStorage(state, addr, func(key, value common.Hash) bool { forEachStorage(state, addr, func(key, value common.Hash) bool {
@@ -513,12 +618,49 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
forEachStorage(checkstate, addr, func(key, value common.Hash) bool { forEachStorage(checkstate, addr, func(key, value common.Hash) bool {
return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value) return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value)
}) })
other := checkstate.getStateObject(addr)
// Check dirty storage which is not in trie
if !maps.Equal(obj.dirtyStorage, other.dirtyStorage) {
print := func(dirty map[common.Hash]common.Hash) string {
var keys []common.Hash
out := new(strings.Builder)
for key := range dirty {
keys = append(keys, key)
}
slices.SortFunc(keys, common.Hash.Cmp)
for i, key := range keys {
fmt.Fprintf(out, " %d. %v %v\n", i, key, dirty[key])
}
return out.String()
}
return fmt.Errorf("dirty storage err, have\n%v\nwant\n%v",
print(obj.dirtyStorage),
print(other.dirtyStorage))
}
}
// Check transient storage.
{
have := state.transientStorage
want := checkstate.transientStorage
eq := maps.EqualFunc(have, want,
func(a Storage, b Storage) bool {
return maps.Equal(a, b)
})
if !eq {
return fmt.Errorf("transient storage differs ,have\n%v\nwant\n%v",
have.PrettyPrint(),
want.PrettyPrint())
}
} }
if err != nil { if err != nil {
return err return err
} }
} }
if !checkstate.accessList.Equal(state.accessList) { // Check access lists
return fmt.Errorf("AccessLists are wrong, have \n%v\nwant\n%v",
checkstate.accessList.PrettyPrint(),
state.accessList.PrettyPrint())
}
if state.GetRefund() != checkstate.GetRefund() { if state.GetRefund() != checkstate.GetRefund() {
return fmt.Errorf("got GetRefund() == %d, want GetRefund() == %d", return fmt.Errorf("got GetRefund() == %d, want GetRefund() == %d",
state.GetRefund(), checkstate.GetRefund()) state.GetRefund(), checkstate.GetRefund())
@@ -527,6 +669,23 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v", return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{})) state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}))
} }
if !maps.Equal(state.journal.dirties, checkstate.journal.dirties) {
getKeys := func(dirty map[common.Address]int) string {
var keys []common.Address
out := new(strings.Builder)
for key := range dirty {
keys = append(keys, key)
}
slices.SortFunc(keys, common.Address.Cmp)
for i, key := range keys {
fmt.Fprintf(out, " %d. %v\n", i, key)
}
return out.String()
}
have := getKeys(state.journal.dirties)
want := getKeys(checkstate.journal.dirties)
return fmt.Errorf("dirty-journal set mismatch.\nhave:\n%v\nwant:\n%v\n", have, want)
}
return nil return nil
} }
@@ -709,18 +868,19 @@ func TestCopyCopyCommitCopy(t *testing.T) {
} }
} }
// TestCommitCopy tests the copy from a committed state is not functional. // TestCommitCopy tests the copy from a committed state is not fully functional.
func TestCommitCopy(t *testing.T) { func TestCommitCopy(t *testing.T) {
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) db := NewDatabase(rawdb.NewMemoryDatabase())
state, _ := New(types.EmptyRootHash, db, nil)
// Create an account and check if the retrieved balance is correct // Create an account and check if the retrieved balance is correct
addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
skey := common.HexToHash("aaa") skey1, skey2 := common.HexToHash("a1"), common.HexToHash("a2")
sval := common.HexToHash("bbb") sval1, sval2 := common.HexToHash("b1"), common.HexToHash("b2")
state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetCode(addr, []byte("hello")) // Change an external metadata
state.SetState(addr, skey, sval) // Change the storage trie state.SetState(addr, skey1, sval1) // Change the storage trie
if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
@@ -728,25 +888,38 @@ func TestCommitCopy(t *testing.T) {
if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) { if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello")) t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello"))
} }
if val := state.GetState(addr, skey); val != sval { if val := state.GetState(addr, skey1); val != sval1 {
t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval) t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval1)
} }
if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) { if val := state.GetCommittedState(addr, skey1); val != (common.Hash{}) {
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{}) t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
} }
// Copy the committed state database, the copied one is not functional. root, _ := state.Commit(0, true)
state.Commit(0, true)
state, _ = New(root, db, nil)
state.SetState(addr, skey2, sval2)
state.Commit(1, true)
// Copy the committed state database, the copied one is not fully functional.
copied := state.Copy() copied := state.Copy()
if balance := copied.GetBalance(addr); balance.Cmp(uint256.NewInt(0)) != 0 { if balance := copied.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
t.Fatalf("unexpected balance: have %v", balance) t.Fatalf("unexpected balance: have %v", balance)
} }
if code := copied.GetCode(addr); code != nil { if code := copied.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
t.Fatalf("unexpected code: have %x", code) t.Fatalf("unexpected code: have %x", code)
} }
if val := copied.GetState(addr, skey); val != (common.Hash{}) { // Miss slots because of non-functional trie after commit
if val := copied.GetState(addr, skey1); val != (common.Hash{}) {
t.Fatalf("unexpected storage slot: have %x", sval1)
}
if val := copied.GetCommittedState(addr, skey1); val != (common.Hash{}) {
t.Fatalf("unexpected storage slot: have %x", val) t.Fatalf("unexpected storage slot: have %x", val)
} }
if val := copied.GetCommittedState(addr, skey); val != (common.Hash{}) { // Slots cached in the stateDB, available after commit
if val := copied.GetState(addr, skey2); val != sval2 {
t.Fatalf("unexpected storage slot: have %x", sval1)
}
if val := copied.GetCommittedState(addr, skey2); val != sval2 {
t.Fatalf("unexpected storage slot: have %x", val) t.Fatalf("unexpected storage slot: have %x", val)
} }
if !errors.Is(copied.Error(), trie.ErrCommitted) { if !errors.Is(copied.Error(), trie.ErrCommitted) {
@@ -1103,40 +1276,6 @@ func TestStateDBTransientStorage(t *testing.T) {
} }
} }
func TestResetObject(t *testing.T) {
var (
disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, nil)
db = NewDatabaseWithNodeDB(disk, tdb)
snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash)
state, _ = New(types.EmptyRootHash, db, snaps)
addr = common.HexToAddress("0x1")
slotA = common.HexToHash("0x1")
slotB = common.HexToHash("0x2")
)
// Initialize account with balance and storage in first transaction.
state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
state.SetState(addr, slotA, common.BytesToHash([]byte{0x1}))
state.IntermediateRoot(true)
// Reset account and mutate balance and storages
state.CreateAccount(addr)
state.SetBalance(addr, uint256.NewInt(2), tracing.BalanceChangeUnspecified)
state.SetState(addr, slotB, common.BytesToHash([]byte{0x2}))
root, _ := state.Commit(0, true)
// Ensure the original account is wiped properly
snap := snaps.Snapshot(root)
slot, _ := snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotA.Bytes()))
if len(slot) != 0 {
t.Fatalf("Unexpected storage slot")
}
slot, _ = snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotB.Bytes()))
if !bytes.Equal(slot, []byte{0x2}) {
t.Fatalf("Unexpected storage slot value %v", slot)
}
}
func TestDeleteStorage(t *testing.T) { func TestDeleteStorage(t *testing.T) {
var ( var (
disk = rawdb.NewMemoryDatabase() disk = rawdb.NewMemoryDatabase()

View File

@@ -17,6 +17,10 @@
package state package state
import ( import (
"fmt"
"slices"
"strings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
@@ -30,10 +34,19 @@ func newTransientStorage() transientStorage {
// Set sets the transient-storage `value` for `key` at the given `addr`. // Set sets the transient-storage `value` for `key` at the given `addr`.
func (t transientStorage) Set(addr common.Address, key, value common.Hash) { func (t transientStorage) Set(addr common.Address, key, value common.Hash) {
if _, ok := t[addr]; !ok { if value == (common.Hash{}) { // this is a 'delete'
t[addr] = make(Storage) if _, ok := t[addr]; ok {
delete(t[addr], key)
if len(t[addr]) == 0 {
delete(t, addr)
}
}
} else {
if _, ok := t[addr]; !ok {
t[addr] = make(Storage)
}
t[addr][key] = value
} }
t[addr][key] = value
} }
// Get gets the transient storage for `key` at the given `addr`. // Get gets the transient storage for `key` at the given `addr`.
@@ -53,3 +66,27 @@ func (t transientStorage) Copy() transientStorage {
} }
return storage return storage
} }
// PrettyPrint prints the contents of the access list in a human-readable form
func (t transientStorage) PrettyPrint() string {
out := new(strings.Builder)
var sortedAddrs []common.Address
for addr := range t {
sortedAddrs = append(sortedAddrs, addr)
slices.SortFunc(sortedAddrs, common.Address.Cmp)
}
for _, addr := range sortedAddrs {
fmt.Fprintf(out, "%#x:", addr)
var sortedKeys []common.Hash
storage := t[addr]
for key := range storage {
sortedKeys = append(sortedKeys, key)
}
slices.SortFunc(sortedKeys, common.Hash.Cmp)
for _, key := range sortedKeys {
fmt.Fprintf(out, " %X : %X\n", key, storage[key])
}
}
return out.String()
}

View File

@@ -186,6 +186,13 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root // ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root
// contract. This method is exported to be used in tests. // contract. This method is exported to be used in tests.
func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *state.StateDB) { func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *state.StateDB) {
if vmenv.Config.Tracer != nil && vmenv.Config.Tracer.OnSystemCallStart != nil {
vmenv.Config.Tracer.OnSystemCallStart()
}
if vmenv.Config.Tracer != nil && vmenv.Config.Tracer.OnSystemCallEnd != nil {
defer vmenv.Config.Tracer.OnSystemCallEnd()
}
// If EIP-4788 is enabled, we need to invoke the beaconroot storage contract with // If EIP-4788 is enabled, we need to invoke the beaconroot storage contract with
// the new root // the new root
msg := &Message{ msg := &Message{

View File

@@ -417,10 +417,11 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
header.ParentBeaconRoot = &beaconRoot header.ParentBeaconRoot = &beaconRoot
} }
// Assemble and return the final block for sealing // Assemble and return the final block for sealing
body := &types.Body{Transactions: txs}
if config.IsShanghai(header.Number, header.Time) { if config.IsShanghai(header.Number, header.Time) {
return types.NewBlockWithWithdrawals(header, txs, nil, receipts, []*types.Withdrawal{}, trie.NewStackTrie(nil)) body.Withdrawals = []*types.Withdrawal{}
} }
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)) return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
} }
var ( var (

View File

@@ -4,6 +4,15 @@ All notable changes to the tracing interface will be documented in this file.
## [Unreleased] ## [Unreleased]
There have been minor backwards-compatible changes to the tracing interface to explicitly mark the execution of **system** contracts. As of now the only system call updates the parent beacon block root as per [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788). Other system calls are being considered for the future hardfork.
### New methods
- `OnSystemCallStart()`: This hook is called when EVM starts processing a system call. Note system calls happen outside the scope of a transaction. This event will be followed by normal EVM execution events.
- `OnSystemCallEnd()`: This hook is called when EVM finishes processing a system call.
## [v1.14.0]
There has been a major breaking change in the tracing interface for custom native tracers. JS and built-in tracers are not affected by this change and tracing API methods may be used as before. This overhaul has been done as part of the new live tracing feature ([#29189](https://github.com/ethereum/go-ethereum/pull/29189)). To learn more about live tracing please refer to the [docs](https://geth.ethereum.org/docs/developers/evm-tracing/live-tracing). There has been a major breaking change in the tracing interface for custom native tracers. JS and built-in tracers are not affected by this change and tracing API methods may be used as before. This overhaul has been done as part of the new live tracing feature ([#29189](https://github.com/ethereum/go-ethereum/pull/29189)). To learn more about live tracing please refer to the [docs](https://geth.ethereum.org/docs/developers/evm-tracing/live-tracing).
**The `EVMLogger` interface which the tracers implemented has been removed.** It has been replaced by a new struct `tracing.Hooks`. `Hooks` keeps pointers to event listening functions. Internally the EVM will use these function pointers to emit events and can skip an event if the tracer has opted not to implement it. In fact this is the main reason for this change of approach. Another benefit is the ease of adding new hooks in future, and dynamically assigning event receivers. **The `EVMLogger` interface which the tracers implemented has been removed.** It has been replaced by a new struct `tracing.Hooks`. `Hooks` keeps pointers to event listening functions. Internally the EVM will use these function pointers to emit events and can skip an event if the tracer has opted not to implement it. In fact this is the main reason for this change of approach. Another benefit is the ease of adding new hooks in future, and dynamically assigning event receivers.
@@ -66,4 +75,5 @@ The hooks `CaptureStart` and `CaptureEnd` have been removed. These hooks signale
- `CaptureState` -> `OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error)`. `op` is of type `byte` which can be cast to `vm.OpCode` when necessary. A `*vm.ScopeContext` is not passed anymore. It is replaced by `tracing.OpContext` which offers access to the memory, stack and current contract. - `CaptureState` -> `OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error)`. `op` is of type `byte` which can be cast to `vm.OpCode` when necessary. A `*vm.ScopeContext` is not passed anymore. It is replaced by `tracing.OpContext` which offers access to the memory, stack and current contract.
- `CaptureFault` -> `OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error)`. Similar to above. - `CaptureFault` -> `OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error)`. Similar to above.
[unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.13.14...master [unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.14.0...master
[v1.14.0]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.0

View File

@@ -81,6 +81,10 @@ type (
TxEndHook = func(receipt *types.Receipt, err error) TxEndHook = func(receipt *types.Receipt, err error)
// EnterHook is invoked when the processing of a message starts. // EnterHook is invoked when the processing of a message starts.
//
// Take note that EnterHook, when in the context of a live tracer, can be invoked
// outside of the `OnTxStart` and `OnTxEnd` hooks when dealing with system calls,
// see [OnSystemCallStartHook] and [OnSystemCallEndHook] for more information.
EnterHook = func(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) EnterHook = func(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
// ExitHook is invoked when the processing of a message ends. // ExitHook is invoked when the processing of a message ends.
@@ -89,6 +93,10 @@ type (
// ran out of gas when attempting to persist the code to database did not // ran out of gas when attempting to persist the code to database did not
// count as a call failure and did not cause a revert of the call. This will // count as a call failure and did not cause a revert of the call. This will
// be indicated by `reverted == false` and `err == ErrCodeStoreOutOfGas`. // be indicated by `reverted == false` and `err == ErrCodeStoreOutOfGas`.
//
// Take note that ExitHook, when in the context of a live tracer, can be invoked
// outside of the `OnTxStart` and `OnTxEnd` hooks when dealing with system calls,
// see [OnSystemCallStartHook] and [OnSystemCallEndHook] for more information.
ExitHook = func(depth int, output []byte, gasUsed uint64, err error, reverted bool) ExitHook = func(depth int, output []byte, gasUsed uint64, err error, reverted bool)
// OpcodeHook is invoked just prior to the execution of an opcode. // OpcodeHook is invoked just prior to the execution of an opcode.
@@ -125,6 +133,22 @@ type (
// GenesisBlockHook is called when the genesis block is being processed. // GenesisBlockHook is called when the genesis block is being processed.
GenesisBlockHook = func(genesis *types.Block, alloc types.GenesisAlloc) GenesisBlockHook = func(genesis *types.Block, alloc types.GenesisAlloc)
// OnSystemCallStartHook is called when a system call is about to be executed. Today,
// this hook is invoked when the EIP-4788 system call is about to be executed to set the
// beacon block root.
//
// After this hook, the EVM call tracing will happened as usual so you will receive a `OnEnter/OnExit`
// as well as state hooks between this hook and the `OnSystemCallEndHook`.
//
// Note that system call happens outside normal transaction execution, so the `OnTxStart/OnTxEnd` hooks
// will not be invoked.
OnSystemCallStartHook = func()
// OnSystemCallEndHook is called when a system call has finished executing. Today,
// this hook is invoked when the EIP-4788 system call is about to be executed to set the
// beacon block root.
OnSystemCallEndHook = func()
/* /*
- State events - - State events -
*/ */
@@ -155,12 +179,14 @@ type Hooks struct {
OnFault FaultHook OnFault FaultHook
OnGasChange GasChangeHook OnGasChange GasChangeHook
// Chain events // Chain events
OnBlockchainInit BlockchainInitHook OnBlockchainInit BlockchainInitHook
OnClose CloseHook OnClose CloseHook
OnBlockStart BlockStartHook OnBlockStart BlockStartHook
OnBlockEnd BlockEndHook OnBlockEnd BlockEndHook
OnSkippedBlock SkippedBlockHook OnSkippedBlock SkippedBlockHook
OnGenesisBlock GenesisBlockHook OnGenesisBlock GenesisBlockHook
OnSystemCallStart OnSystemCallStartHook
OnSystemCallEnd OnSystemCallEndHook
// State events // State events
OnBalanceChange BalanceChangeHook OnBalanceChange BalanceChangeHook
OnNonceChange NonceChangeHook OnNonceChange NonceChangeHook

View File

@@ -18,7 +18,6 @@ package core
import ( import (
"math/big" "math/big"
"os"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@@ -211,8 +210,7 @@ func TestTxIndexer(t *testing.T) {
}, },
} }
for _, c := range cases { for _, c := range cases {
frdir := t.TempDir() db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false)
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), big.NewInt(0)) rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), big.NewInt(0))
// Index the initial blocks from ancient store // Index the initial blocks from ancient store
@@ -238,6 +236,5 @@ func TestTxIndexer(t *testing.T) {
verify(db, 0, indexer) verify(db, 0, indexer)
db.Close() db.Close()
os.RemoveAll(frdir)
} }
} }

View File

@@ -87,7 +87,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header {
} }
func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
return types.NewBlock(bc.CurrentBlock(), nil, nil, nil, trie.NewStackTrie(nil)) return types.NewBlock(bc.CurrentBlock(), nil, nil, trie.NewStackTrie(nil))
} }
func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) {

View File

@@ -23,6 +23,7 @@ import (
"io" "io"
"math/big" "math/big"
"reflect" "reflect"
"slices"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -217,13 +218,19 @@ type extblock struct {
// NewBlock creates a new block. The input data is copied, changes to header and to the // NewBlock creates a new block. The input data is copied, changes to header and to the
// field values will not affect the block. // field values will not affect the block.
// //
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header // The body elements and the receipts are used to recompute and overwrite the
// are ignored and set to values derived from the given txs, uncles // relevant portions of the header.
// and receipts. func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block {
func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher) *Block { if body == nil {
b := &Block{header: CopyHeader(header)} body = &Body{}
}
var (
b = NewBlockWithHeader(header)
txs = body.Transactions
uncles = body.Uncles
withdrawals = body.Withdrawals
)
// TODO: panic if len(txs) != len(receipts)
if len(txs) == 0 { if len(txs) == 0 {
b.header.TxHash = EmptyTxsHash b.header.TxHash = EmptyTxsHash
} else { } else {
@@ -249,27 +256,18 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*
} }
} }
return b
}
// NewBlockWithWithdrawals creates a new block with withdrawals. The input data is copied,
// changes to header and to the field values will not affect the block.
//
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header are ignored and set to
// values derived from the given txs, uncles and receipts.
func NewBlockWithWithdrawals(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, withdrawals []*Withdrawal, hasher TrieHasher) *Block {
b := NewBlock(header, txs, uncles, receipts, hasher)
if withdrawals == nil { if withdrawals == nil {
b.header.WithdrawalsHash = nil b.header.WithdrawalsHash = nil
} else if len(withdrawals) == 0 { } else if len(withdrawals) == 0 {
b.header.WithdrawalsHash = &EmptyWithdrawalsHash b.header.WithdrawalsHash = &EmptyWithdrawalsHash
b.withdrawals = Withdrawals{}
} else { } else {
h := DeriveSha(Withdrawals(withdrawals), hasher) hash := DeriveSha(Withdrawals(withdrawals), hasher)
b.header.WithdrawalsHash = &h b.header.WithdrawalsHash = &hash
b.withdrawals = slices.Clone(withdrawals)
} }
return b.WithWithdrawals(withdrawals) return b
} }
// CopyHeader creates a deep copy of a block header. // CopyHeader creates a deep copy of a block header.
@@ -453,31 +451,17 @@ func (b *Block) WithSeal(header *Header) *Block {
} }
} }
// WithBody returns a copy of the block with the given transaction and uncle contents. // WithBody returns a new block with the original header and a deep copy of the
func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block { // provided body.
func (b *Block) WithBody(body Body) *Block {
block := &Block{ block := &Block{
header: b.header, header: b.header,
transactions: make([]*Transaction, len(transactions)), transactions: slices.Clone(body.Transactions),
uncles: make([]*Header, len(uncles)), uncles: make([]*Header, len(body.Uncles)),
withdrawals: b.withdrawals, withdrawals: slices.Clone(body.Withdrawals),
} }
copy(block.transactions, transactions) for i := range body.Uncles {
for i := range uncles { block.uncles[i] = CopyHeader(body.Uncles[i])
block.uncles[i] = CopyHeader(uncles[i])
}
return block
}
// WithWithdrawals returns a copy of the block containing the given withdrawals.
func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block {
block := &Block{
header: b.header,
transactions: b.transactions,
uncles: b.uncles,
}
if withdrawals != nil {
block.withdrawals = make([]*Withdrawal, len(withdrawals))
copy(block.withdrawals, withdrawals)
} }
return block return block
} }

View File

@@ -254,7 +254,7 @@ func makeBenchBlock() *Block {
Extra: []byte("benchmark uncle"), Extra: []byte("benchmark uncle"),
} }
} }
return NewBlock(header, txs, uncles, receipts, blocktest.NewHasher()) return NewBlock(header, &Body{Transactions: txs, Uncles: uncles}, receipts, blocktest.NewHasher())
} }
func TestRlpDecodeParentHash(t *testing.T) { func TestRlpDecodeParentHash(t *testing.T) {

View File

@@ -705,6 +705,8 @@ func (c *bls12381G1Add) Run(input []byte) ([]byte, error) {
return nil, err return nil, err
} }
// No need to check the subgroup here, as specified by EIP-2537
// Compute r = p_0 + p_1 // Compute r = p_0 + p_1
p0.Add(p0, p1) p0.Add(p0, p1)
@@ -734,6 +736,11 @@ func (c *bls12381G1Mul) Run(input []byte) ([]byte, error) {
if p0, err = decodePointG1(input[:128]); err != nil { if p0, err = decodePointG1(input[:128]); err != nil {
return nil, err return nil, err
} }
// 'point is on curve' check already done,
// Here we need to apply subgroup checks.
if !p0.IsInSubGroup() {
return nil, errBLS12381G1PointSubgroup
}
// Decode scalar value // Decode scalar value
e := new(big.Int).SetBytes(input[128:]) e := new(big.Int).SetBytes(input[128:])
@@ -787,6 +794,11 @@ func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// 'point is on curve' check already done,
// Here we need to apply subgroup checks.
if !p.IsInSubGroup() {
return nil, errBLS12381G1PointSubgroup
}
points[i] = *p points[i] = *p
// Decode scalar value // Decode scalar value
scalars[i] = *new(fr.Element).SetBytes(input[t1:t2]) scalars[i] = *new(fr.Element).SetBytes(input[t1:t2])
@@ -827,6 +839,8 @@ func (c *bls12381G2Add) Run(input []byte) ([]byte, error) {
return nil, err return nil, err
} }
// No need to check the subgroup here, as specified by EIP-2537
// Compute r = p_0 + p_1 // Compute r = p_0 + p_1
r := new(bls12381.G2Affine) r := new(bls12381.G2Affine)
r.Add(p0, p1) r.Add(p0, p1)
@@ -857,6 +871,11 @@ func (c *bls12381G2Mul) Run(input []byte) ([]byte, error) {
if p0, err = decodePointG2(input[:256]); err != nil { if p0, err = decodePointG2(input[:256]); err != nil {
return nil, err return nil, err
} }
// 'point is on curve' check already done,
// Here we need to apply subgroup checks.
if !p0.IsInSubGroup() {
return nil, errBLS12381G2PointSubgroup
}
// Decode scalar value // Decode scalar value
e := new(big.Int).SetBytes(input[256:]) e := new(big.Int).SetBytes(input[256:])
@@ -910,6 +929,11 @@ func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// 'point is on curve' check already done,
// Here we need to apply subgroup checks.
if !p.IsInSubGroup() {
return nil, errBLS12381G2PointSubgroup
}
points[i] = *p points[i] = *p
// Decode scalar value // Decode scalar value
scalars[i] = *new(fr.Element).SetBytes(input[t1:t2]) scalars[i] = *new(fr.Element).SetBytes(input[t1:t2])
@@ -1099,9 +1123,6 @@ func (c *bls12381MapG1) Run(input []byte) ([]byte, error) {
// Compute mapping // Compute mapping
r := bls12381.MapToG1(fe) r := bls12381.MapToG1(fe)
if err != nil {
return nil, err
}
// Encode the G1 point to 128 bytes // Encode the G1 point to 128 bytes
return encodePointG1(&r), nil return encodePointG1(&r), nil
@@ -1135,9 +1156,6 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) {
// Compute mapping // Compute mapping
r := bls12381.MapToG2(bls12381.E2{A0: c0, A1: c1}) r := bls12381.MapToG2(bls12381.E2{A0: c0, A1: c1})
if err != nil {
return nil, err
}
// Encode the G2 point to 256 bytes // Encode the G2 point to 256 bytes
return encodePointG2(&r), nil return encodePointG2(&r), nil

View File

@@ -436,14 +436,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
return nil, common.Address{}, gas, ErrNonceUintOverflow return nil, common.Address{}, gas, ErrNonceUintOverflow
} }
evm.StateDB.SetNonce(caller.Address(), nonce+1) evm.StateDB.SetNonce(caller.Address(), nonce+1)
// We add this to the access list _before_ taking a snapshot. Even if the creation fails,
// the access-list change should not be rolled back // We add this to the access list _before_ taking a snapshot. Even if the
// creation fails, the access-list change should not be rolled back.
if evm.chainRules.IsBerlin { if evm.chainRules.IsBerlin {
evm.StateDB.AddAddressToAccessList(address) evm.StateDB.AddAddressToAccessList(address)
} }
// Ensure there's no existing contract already at the designated address. // Ensure there's no existing contract already at the designated address.
// Account is regarded as existent if any of these three conditions is met: // Account is regarded as existent if any of these three conditions is met:
// - the nonce is nonzero // - the nonce is non-zero
// - the code is non-empty // - the code is non-empty
// - the storage is non-empty // - the storage is non-empty
contractHash := evm.StateDB.GetCodeHash(address) contractHash := evm.StateDB.GetCodeHash(address)
@@ -456,9 +457,19 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
} }
return nil, common.Address{}, 0, ErrContractAddressCollision return nil, common.Address{}, 0, ErrContractAddressCollision
} }
// Create a new account on the state // Create a new account on the state only if the object was not present.
// It might be possible the contract code is deployed to a pre-existent
// account with non-zero balance.
snapshot := evm.StateDB.Snapshot() snapshot := evm.StateDB.Snapshot()
evm.StateDB.CreateAccount(address) if !evm.StateDB.Exist(address) {
evm.StateDB.CreateAccount(address)
}
// CreateContract means that regardless of whether the account previously existed
// in the state trie or not, it _now_ becomes created as a _contract_ account.
// This is performed _prior_ to executing the initcode, since the initcode
// acts inside that account.
evm.StateDB.CreateContract(address)
if evm.chainRules.IsEIP158 { if evm.chainRules.IsEIP158 {
evm.StateDB.SetNonce(address, 1) evm.StateDB.SetNonce(address, 1)
} }

View File

@@ -173,11 +173,7 @@ func opByte(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
func opAddmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { func opAddmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek() x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek()
if z.IsZero() { z.AddMod(&x, &y, z)
z.Clear()
} else {
z.AddMod(&x, &y, z)
}
return nil, nil return nil, nil
} }

View File

@@ -29,6 +29,7 @@ import (
// StateDB is an EVM database for full state querying. // StateDB is an EVM database for full state querying.
type StateDB interface { type StateDB interface {
CreateAccount(common.Address) CreateAccount(common.Address)
CreateContract(common.Address)
SubBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) SubBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason)
AddBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) AddBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason)

View File

@@ -779,7 +779,7 @@ func setBlockhash(data *engine.ExecutableData) *engine.ExecutableData {
Extra: data.ExtraData, Extra: data.ExtraData,
MixDigest: data.Random, MixDigest: data.Random,
} }
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs})
data.BlockHash = block.Hash() data.BlockHash = block.Hash()
return data return data
} }
@@ -935,7 +935,7 @@ func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) {
Extra: data.ExtraData, Extra: data.ExtraData,
MixDigest: data.Random, MixDigest: data.Random,
} }
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs})
data.BlockHash = block.Hash() data.BlockHash = block.Hash()
// Send the new payload // Send the new payload
resp2, err := api.NewPayloadV1(data) resp2, err := api.NewPayloadV1(data)
@@ -1554,7 +1554,7 @@ func TestBlockToPayloadWithBlobs(t *testing.T) {
}, },
} }
block := types.NewBlock(&header, txs, nil, nil, trie.NewStackTrie(nil)) block := types.NewBlock(&header, &types.Body{Transactions: txs}, nil, trie.NewStackTrie(nil))
envelope := engine.BlockToExecutableData(block, nil, sidecars) envelope := engine.BlockToExecutableData(block, nil, sidecars)
var want int var want int
for _, tx := range txs { for _, tx := range txs {

View File

@@ -106,7 +106,7 @@ func (b *beaconBackfiller) resume() {
}() }()
// If the downloader fails, report an error as in beacon chain mode there // If the downloader fails, report an error as in beacon chain mode there
// should be no errors as long as the chain we're syncing to is valid. // should be no errors as long as the chain we're syncing to is valid.
if err := b.downloader.synchronise("", common.Hash{}, nil, nil, mode, true, b.started); err != nil { if err := b.downloader.synchronise(mode, b.started); err != nil {
log.Error("Beacon backfilling failed", "err", err) log.Error("Beacon backfilling failed", "err", err)
return return
} }
@@ -268,9 +268,9 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) {
return start, nil return start, nil
} }
// fetchBeaconHeaders feeds skeleton headers to the downloader queue for scheduling // fetchHeaders feeds skeleton headers to the downloader queue for scheduling
// until sync errors or is finished. // until sync errors or is finished.
func (d *Downloader) fetchBeaconHeaders(from uint64) error { func (d *Downloader) fetchHeaders(from uint64) error {
var head *types.Header var head *types.Header
_, tail, _, err := d.skeleton.Bounds() _, tail, _, err := d.skeleton.Bounds()
if err != nil { if err != nil {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -68,48 +68,3 @@ func (d *Downloader) fetchHeadersByHash(p *peerConnection, hash common.Hash, amo
return *res.Res.(*eth.BlockHeadersRequest), res.Meta.([]common.Hash), nil return *res.Res.(*eth.BlockHeadersRequest), res.Meta.([]common.Hash), nil
} }
} }
// fetchHeadersByNumber is a blocking version of Peer.RequestHeadersByNumber which
// handles all the cancellation, interruption and timeout mechanisms of a data
// retrieval to allow blocking API calls.
func (d *Downloader) fetchHeadersByNumber(p *peerConnection, number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error) {
// Create the response sink and send the network request
start := time.Now()
resCh := make(chan *eth.Response)
req, err := p.peer.RequestHeadersByNumber(number, amount, skip, reverse, resCh)
if err != nil {
return nil, nil, err
}
defer req.Close()
// Wait until the response arrives, the request is cancelled or times out
ttl := d.peers.rates.TargetTimeout()
timeoutTimer := time.NewTimer(ttl)
defer timeoutTimer.Stop()
select {
case <-d.cancelCh:
return nil, nil, errCanceled
case <-timeoutTimer.C:
// Header retrieval timed out, update the metrics
p.log.Debug("Header request timed out", "elapsed", ttl)
headerTimeoutMeter.Mark(1)
return nil, nil, errTimeout
case res := <-resCh:
// Headers successfully retrieved, update the metrics
headerReqTimer.Update(time.Since(start))
headerInMeter.Mark(int64(len(*res.Res.(*eth.BlockHeadersRequest))))
// Don't reject the packet even if it turns out to be bad, downloader will
// disconnect the peer on its own terms. Simply delivery the headers to
// be processed by the caller
res.Done <- nil
return *res.Res.(*eth.BlockHeadersRequest), res.Meta.([]common.Hash), nil
}
}

View File

@@ -76,7 +76,7 @@ type typedQueue interface {
// concurrentFetch iteratively downloads scheduled block parts, taking available // concurrentFetch iteratively downloads scheduled block parts, taking available
// peers, reserving a chunk of fetch requests for each and waiting for delivery // peers, reserving a chunk of fetch requests for each and waiting for delivery
// or timeouts. // or timeouts.
func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error { func (d *Downloader) concurrentFetch(queue typedQueue) error {
// Create a delivery channel to accept responses from all peers // Create a delivery channel to accept responses from all peers
responses := make(chan *eth.Response) responses := make(chan *eth.Response)
@@ -126,10 +126,6 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error {
// Prepare the queue and fetch block parts until the block header fetcher's done // Prepare the queue and fetch block parts until the block header fetcher's done
finished := false finished := false
for { for {
// Short circuit if we lost all our peers
if d.peers.Len() == 0 && !beaconMode {
return errNoPeers
}
// If there's nothing more to fetch, wait or terminate // If there's nothing more to fetch, wait or terminate
if queue.pending() == 0 { if queue.pending() == 0 {
if len(pending) == 0 && finished { if len(pending) == 0 && finished {
@@ -158,27 +154,20 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error {
} }
sort.Sort(&peerCapacitySort{idles, caps}) sort.Sort(&peerCapacitySort{idles, caps})
var ( var throttled bool
progressed bool
throttled bool
queued = queue.pending()
)
for _, peer := range idles { for _, peer := range idles {
// Short circuit if throttling activated or there are no more // Short circuit if throttling activated or there are no more
// queued tasks to be retrieved // queued tasks to be retrieved
if throttled { if throttled {
break break
} }
if queued = queue.pending(); queued == 0 { if queued := queue.pending(); queued == 0 {
break break
} }
// Reserve a chunk of fetches for a peer. A nil can mean either that // Reserve a chunk of fetches for a peer. A nil can mean either that
// no more headers are available, or that the peer is known not to // no more headers are available, or that the peer is known not to
// have them. // have them.
request, progress, throttle := queue.reserve(peer, queue.capacity(peer, d.peers.rates.TargetRoundTrip())) request, _, throttle := queue.reserve(peer, queue.capacity(peer, d.peers.rates.TargetRoundTrip()))
if progress {
progressed = true
}
if throttle { if throttle {
throttled = true throttled = true
throttleCounter.Inc(1) throttleCounter.Inc(1)
@@ -207,11 +196,6 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error {
timeout.Reset(ttl) timeout.Reset(ttl)
} }
} }
// Make sure that we have peers available for fetching. If all peers have been tried
// and all failed throw an error
if !progressed && !throttled && len(pending) == 0 && len(idles) == d.peers.Len() && queued > 0 && !beaconMode {
return errPeersUnavailable
}
} }
// Wait for something to happen // Wait for something to happen
select { select {
@@ -315,16 +299,6 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error {
queue.updateCapacity(peer, 0, 0) queue.updateCapacity(peer, 0, 0)
} else { } else {
d.dropPeer(peer.id) d.dropPeer(peer.id)
// If this peer was the master peer, abort sync immediately
d.cancelLock.RLock()
master := peer.id == d.cancelPeer
d.cancelLock.RUnlock()
if master {
d.cancel()
return errTimeout
}
} }
case res := <-responses: case res := <-responses:

View File

@@ -78,7 +78,6 @@ func (q *bodyQueue) request(peer *peerConnection, req *fetchRequest, resCh chan
if q.bodyFetchHook != nil { if q.bodyFetchHook != nil {
q.bodyFetchHook(req.Headers) q.bodyFetchHook(req.Headers)
} }
hashes := make([]common.Hash, 0, len(req.Headers)) hashes := make([]common.Hash, 0, len(req.Headers))
for _, header := range req.Headers { for _, header := range req.Headers {
hashes = append(hashes, header.Hash()) hashes = append(hashes, header.Hash())

View File

@@ -1,97 +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 downloader
import (
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/log"
)
// headerQueue implements typedQueue and is a type adapter between the generic
// concurrent fetcher and the downloader.
type headerQueue Downloader
// waker returns a notification channel that gets pinged in case more header
// fetches have been queued up, so the fetcher might assign it to idle peers.
func (q *headerQueue) waker() chan bool {
return q.queue.headerContCh
}
// pending returns the number of headers that are currently queued for fetching
// by the concurrent downloader.
func (q *headerQueue) pending() int {
return q.queue.PendingHeaders()
}
// capacity is responsible for calculating how many headers a particular peer is
// estimated to be able to retrieve within the allotted round trip time.
func (q *headerQueue) capacity(peer *peerConnection, rtt time.Duration) int {
return peer.HeaderCapacity(rtt)
}
// updateCapacity is responsible for updating how many headers a particular peer
// is estimated to be able to retrieve in a unit time.
func (q *headerQueue) updateCapacity(peer *peerConnection, items int, span time.Duration) {
peer.UpdateHeaderRate(items, span)
}
// reserve is responsible for allocating a requested number of pending headers
// from the download queue to the specified peer.
func (q *headerQueue) reserve(peer *peerConnection, items int) (*fetchRequest, bool, bool) {
return q.queue.ReserveHeaders(peer, items), false, false
}
// unreserve is responsible for removing the current header retrieval allocation
// assigned to a specific peer and placing it back into the pool to allow
// reassigning to some other peer.
func (q *headerQueue) unreserve(peer string) int {
fails := q.queue.ExpireHeaders(peer)
if fails > 2 {
log.Trace("Header delivery timed out", "peer", peer)
} else {
log.Debug("Header delivery stalling", "peer", peer)
}
return fails
}
// request is responsible for converting a generic fetch request into a header
// one and sending it to the remote peer for fulfillment.
func (q *headerQueue) request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error) {
peer.log.Trace("Requesting new batch of headers", "from", req.From)
return peer.peer.RequestHeadersByNumber(req.From, MaxHeaderFetch, 0, false, resCh)
}
// deliver is responsible for taking a generic response packet from the concurrent
// fetcher, unpacking the header data and delivering it to the downloader's queue.
func (q *headerQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) {
headers := *packet.Res.(*eth.BlockHeadersRequest)
hashes := packet.Meta.([]common.Hash)
accepted, err := q.queue.DeliverHeaders(peer.id, headers, hashes, q.headerProcCh)
switch {
case err == nil && len(headers) == 0:
peer.log.Trace("Requested headers delivered")
case err == nil:
peer.log.Trace("Delivered new batch of headers", "count", len(headers), "accepted", accepted)
default:
peer.log.Debug("Failed to deliver retrieved headers", "err", err)
}
return accepted, err
}

View File

@@ -87,6 +87,15 @@ func newFetchResult(header *types.Header, fastSync bool) *fetchResult {
return item return item
} }
// body returns a representation of the fetch result as a types.Body object.
func (f *fetchResult) body() types.Body {
return types.Body{
Transactions: f.Transactions,
Uncles: f.Uncles,
Withdrawals: f.Withdrawals,
}
}
// SetBodyDone flags the body as finished. // SetBodyDone flags the body as finished.
func (f *fetchResult) SetBodyDone() { func (f *fetchResult) SetBodyDone() {
if v := f.pending.Load(); (v & (1 << bodyType)) != 0 { if v := f.pending.Load(); (v & (1 << bodyType)) != 0 {

View File

@@ -58,7 +58,6 @@ var pregenerated bool
func init() { func init() {
// Reduce some of the parameters to make the tester faster // Reduce some of the parameters to make the tester faster
fullMaxForkAncestry = 10000 fullMaxForkAncestry = 10000
lightMaxForkAncestry = 10000
blockCacheMaxItems = 1024 blockCacheMaxItems = 1024
fsHeaderSafetyNet = 256 fsHeaderSafetyNet = 256
fsHeaderContCheck = 500 * time.Millisecond fsHeaderContCheck = 500 * time.Millisecond

View File

@@ -80,6 +80,16 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
} }
available.Sub(available, call.Value) available.Sub(available, call.Value)
} }
if opts.Config.IsCancun(opts.Header.Number, opts.Header.Time) && len(call.BlobHashes) > 0 {
blobGasPerBlob := new(big.Int).SetInt64(params.BlobTxBlobGasPerBlob)
blobBalanceUsage := new(big.Int).SetInt64(int64(len(call.BlobHashes)))
blobBalanceUsage.Mul(blobBalanceUsage, blobGasPerBlob)
blobBalanceUsage.Mul(blobBalanceUsage, call.BlobGasFeeCap)
if blobBalanceUsage.Cmp(available) >= 0 {
return 0, nil, core.ErrInsufficientFunds
}
available.Sub(available, blobBalanceUsage)
}
allowance := new(big.Int).Div(available, feeCap) allowance := new(big.Int).Div(available, feeCap)
// If the allowance is larger than maximum uint64, skip checking // If the allowance is larger than maximum uint64, skip checking

View File

@@ -44,6 +44,7 @@ const (
// maxBlockFetchers is the max number of goroutines to spin up to pull blocks // maxBlockFetchers is the max number of goroutines to spin up to pull blocks
// for the fee history calculation (mostly relevant for LES). // for the fee history calculation (mostly relevant for LES).
maxBlockFetchers = 4 maxBlockFetchers = 4
maxQueryLimit = 100
) )
// blockFees represents a single block for processing // blockFees represents a single block for processing
@@ -240,6 +241,9 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL
if len(rewardPercentiles) != 0 { if len(rewardPercentiles) != 0 {
maxFeeHistory = oracle.maxBlockHistory maxFeeHistory = oracle.maxBlockHistory
} }
if len(rewardPercentiles) > maxQueryLimit {
return common.Big0, nil, nil, nil, nil, nil, fmt.Errorf("%w: over the query limit %d", errInvalidPercentile, maxQueryLimit)
}
if blocks > maxFeeHistory { if blocks > maxFeeHistory {
log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory) log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory)
blocks = maxFeeHistory blocks = maxFeeHistory

View File

@@ -42,7 +42,6 @@ import (
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/triedb/pathdb" "github.com/ethereum/go-ethereum/triedb/pathdb"
"golang.org/x/crypto/sha3"
) )
const ( const (
@@ -480,7 +479,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) {
var ( var (
signer = types.LatestSignerForChainID(h.chain.Config().ChainID) // Don't care about chain status, we just need *a* sender signer = types.LatestSignerForChainID(h.chain.Config().ChainID) // Don't care about chain status, we just need *a* sender
hasher = sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher = crypto.NewKeccakState()
hash = make([]byte, 32) hash = make([]byte, 32)
) )
for _, tx := range txs { for _, tx := range txs {

View File

@@ -190,7 +190,7 @@ func serviceContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHe
return headers return headers
} }
{ // Last mode: deliver ancestors of H { // Last mode: deliver ancestors of H
for i := uint64(1); header != nil && i < count; i++ { for i := uint64(1); i < count; i++ {
header = chain.GetHeaderByHash(header.ParentHash) header = chain.GetHeaderByHash(header.ParentHash)
if header == nil { if header == nil {
break break

View File

@@ -164,7 +164,7 @@ func (t *pathTrie) deleteAccountNode(path []byte, inner bool) {
} else { } else {
accountOuterLookupGauge.Inc(1) accountOuterLookupGauge.Inc(1)
} }
if !rawdb.ExistsAccountTrieNode(t.db, path) { if !rawdb.HasAccountTrieNode(t.db, path) {
return return
} }
if inner { if inner {
@@ -181,7 +181,7 @@ func (t *pathTrie) deleteStorageNode(path []byte, inner bool) {
} else { } else {
storageOuterLookupGauge.Inc(1) storageOuterLookupGauge.Inc(1)
} }
if !rawdb.ExistsStorageTrieNode(t.db, t.owner, path) { if !rawdb.HasStorageTrieNode(t.db, t.owner, path) {
return return
} }
if inner { if inner {

View File

@@ -42,7 +42,6 @@ import (
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"golang.org/x/crypto/sha3"
) )
const ( const (
@@ -2653,7 +2652,7 @@ func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error
// Cross reference the requested bytecodes with the response to find gaps // Cross reference the requested bytecodes with the response to find gaps
// that the serving node is missing // that the serving node is missing
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher := crypto.NewKeccakState()
hash := make([]byte, 32) hash := make([]byte, 32)
codes := make([][]byte, len(req.hashes)) codes := make([][]byte, len(req.hashes))
@@ -2901,7 +2900,7 @@ func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error
// Cross reference the requested trienodes with the response to find gaps // Cross reference the requested trienodes with the response to find gaps
// that the serving node is missing // that the serving node is missing
var ( var (
hasher = sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher = crypto.NewKeccakState()
hash = make([]byte, 32) hash = make([]byte, 32)
nodes = make([][]byte, len(req.hashes)) nodes = make([][]byte, len(req.hashes))
fills uint64 fills uint64
@@ -3007,7 +3006,7 @@ func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) e
// Cross reference the requested bytecodes with the response to find gaps // Cross reference the requested bytecodes with the response to find gaps
// that the serving node is missing // that the serving node is missing
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher := crypto.NewKeccakState()
hash := make([]byte, 32) hash := make([]byte, 32)
codes := make([][]byte, len(req.hashes)) codes := make([][]byte, len(req.hashes))

View File

@@ -64,7 +64,7 @@ func TestHashing(t *testing.T) {
} }
} }
var new = func() { var new = func() {
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher := crypto.NewKeccakState()
var hash = make([]byte, 32) var hash = make([]byte, 32)
for i := 0; i < len(bytecodes); i++ { for i := 0; i < len(bytecodes); i++ {
hasher.Reset() hasher.Reset()
@@ -96,7 +96,7 @@ func BenchmarkHashing(b *testing.B) {
} }
} }
var new = func() { var new = func() {
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) hasher := crypto.NewKeccakState()
var hash = make([]byte, 32) var hash = make([]byte, 32)
for i := 0; i < len(bytecodes); i++ { for i := 0; i < len(bytecodes); i++ {
hasher.Reset() hasher.Reset()

View File

@@ -23,6 +23,7 @@ import (
"math/big" "math/big"
"slices" "slices"
"strings" "strings"
"sync/atomic"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
@@ -114,7 +115,7 @@ type flatCallTracer struct {
tracer *callTracer tracer *callTracer
config flatCallTracerConfig config flatCallTracerConfig
ctx *tracers.Context // Holds tracer context data ctx *tracers.Context // Holds tracer context data
reason error // Textual reason for the interruption interrupt atomic.Bool // Atomic flag to signal execution interruption
activePrecompiles []common.Address // Updated on tx start based on given rules activePrecompiles []common.Address // Updated on tx start based on given rules
} }
@@ -154,6 +155,9 @@ func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Trac
// OnEnter is called when EVM enters a new scope (via call, create or selfdestruct). // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *flatCallTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *flatCallTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if t.interrupt.Load() {
return
}
t.tracer.OnEnter(depth, typ, from, to, input, gas, value) t.tracer.OnEnter(depth, typ, from, to, input, gas, value)
if depth == 0 { if depth == 0 {
@@ -169,6 +173,9 @@ func (t *flatCallTracer) OnEnter(depth int, typ byte, from common.Address, to co
// OnExit is called when EVM exits a scope, even if the scope didn't // OnExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // execute any code.
func (t *flatCallTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { func (t *flatCallTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
if t.interrupt.Load() {
return
}
t.tracer.OnExit(depth, output, gasUsed, err, reverted) t.tracer.OnExit(depth, output, gasUsed, err, reverted)
if depth == 0 { if depth == 0 {
@@ -194,6 +201,9 @@ func (t *flatCallTracer) OnExit(depth int, output []byte, gasUsed uint64, err er
} }
func (t *flatCallTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { func (t *flatCallTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
if t.interrupt.Load() {
return
}
t.tracer.OnTxStart(env, tx, from) t.tracer.OnTxStart(env, tx, from)
// Update list of precompiles based on current block // Update list of precompiles based on current block
rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time) rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time)
@@ -201,6 +211,9 @@ func (t *flatCallTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction
} }
func (t *flatCallTracer) OnTxEnd(receipt *types.Receipt, err error) { func (t *flatCallTracer) OnTxEnd(receipt *types.Receipt, err error) {
if t.interrupt.Load() {
return
}
t.tracer.OnTxEnd(receipt, err) t.tracer.OnTxEnd(receipt, err)
} }
@@ -219,12 +232,13 @@ func (t *flatCallTracer) GetResult() (json.RawMessage, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return res, t.reason return res, t.tracer.reason
} }
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
func (t *flatCallTracer) Stop(err error) { func (t *flatCallTracer) Stop(err error) {
t.tracer.Stop(err) t.tracer.Stop(err)
t.interrupt.Store(true)
} }
// isPrecompiled returns whether the addr is a precompile. // isPrecompiled returns whether the addr is a precompile.

View File

@@ -0,0 +1,64 @@
// 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 native_test
import (
"errors"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"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/params"
"github.com/stretchr/testify/require"
)
func TestCallFlatStop(t *testing.T) {
tracer, err := tracers.DefaultDirectory.New("flatCallTracer", &tracers.Context{}, nil)
require.NoError(t, err)
// this error should be returned by GetResult
stopError := errors.New("stop error")
// simulate a transaction
tx := types.NewTx(&types.LegacyTx{
Nonce: 0,
To: &common.Address{},
Value: big.NewInt(0),
Gas: 0,
GasPrice: big.NewInt(0),
Data: nil,
})
tracer.OnTxStart(&tracing.VMContext{
ChainConfig: params.MainnetChainConfig,
}, tx, common.Address{})
tracer.OnEnter(0, byte(vm.CALL), common.Address{}, common.Address{}, nil, 0, big.NewInt(0))
// stop before the transaction is finished
tracer.Stop(stopError)
tracer.OnTxEnd(&types.Receipt{GasUsed: 0}, nil)
// check that the error is returned by GetResult
_, tracerError := tracer.GetResult()
require.Equal(t, stopError, tracerError)
}

View File

@@ -191,7 +191,12 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface
} }
txs[i] = tx.tx txs[i] = tx.tx
} }
return types.NewBlockWithHeader(head).WithBody(txs, uncles).WithWithdrawals(body.Withdrawals), nil return types.NewBlockWithHeader(head).WithBody(
types.Body{
Transactions: txs,
Uncles: uncles,
Withdrawals: body.Withdrawals,
}), nil
} }
// HeaderByHash returns the block header with the given hash. // HeaderByHash returns the block header with the given hash.

View File

@@ -88,8 +88,8 @@ type AncientReaderOp interface {
// Ancients returns the ancient item numbers in the ancient store. // Ancients returns the ancient item numbers in the ancient store.
Ancients() (uint64, error) Ancients() (uint64, error)
// Tail returns the number of first stored item in the freezer. // Tail returns the number of first stored item in the ancient store.
// This number can also be interpreted as the total deleted item numbers. // This number can also be interpreted as the total deleted items.
Tail() (uint64, error) Tail() (uint64, error)
// AncientSize returns the ancient size of the specified category. // AncientSize returns the ancient size of the specified category.
@@ -101,7 +101,7 @@ type AncientReader interface {
AncientReaderOp AncientReaderOp
// ReadAncients runs the given read operation while ensuring that no writes take place // ReadAncients runs the given read operation while ensuring that no writes take place
// on the underlying freezer. // on the underlying ancient store.
ReadAncients(fn func(AncientReaderOp) error) (err error) ReadAncients(fn func(AncientReaderOp) error) (err error)
} }
@@ -141,11 +141,15 @@ type AncientWriteOp interface {
AppendRaw(kind string, number uint64, item []byte) error AppendRaw(kind string, number uint64, item []byte) error
} }
// AncientStater wraps the Stat method of a backing data store. // AncientStater wraps the Stat method of a backing ancient store.
type AncientStater interface { type AncientStater interface {
// AncientDatadir returns the path of root ancient directory. Empty string // AncientDatadir returns the path of the ancient store directory.
// will be returned if ancient store is not enabled at all. The returned //
// path can be used to construct the path of other freezers. // If the ancient store is not activated, an error is returned.
// If an ephemeral ancient store is used, an empty path is returned.
//
// The path returned by AncientDatadir can be used as the root path
// of the ancient store to construct paths for other sub ancient stores.
AncientDatadir() (string, error) AncientDatadir() (string, error)
} }
@@ -171,15 +175,23 @@ type Stater interface {
} }
// AncientStore contains all the methods required to allow handling different // AncientStore contains all the methods required to allow handling different
// ancient data stores backing immutable chain data store. // ancient data stores backing immutable data store.
type AncientStore interface { type AncientStore interface {
AncientReader AncientReader
AncientWriter AncientWriter
io.Closer io.Closer
} }
// ResettableAncientStore extends the AncientStore interface by adding a Reset method.
type ResettableAncientStore interface {
AncientStore
// Reset is designed to reset the entire ancient store to its default state.
Reset() error
}
// Database contains all the methods required by the high level database to not // Database contains all the methods required by the high level database to not
// only access the key-value data store but also the chain freezer. // only access the key-value data store but also the ancient chain store.
type Database interface { type Database interface {
Reader Reader
Writer Writer

View File

@@ -240,19 +240,19 @@ func New(file string, cache int, handles int, namespace string, readonly bool, e
} }
db.db = innerDB db.db = innerDB
db.compTimeMeter = metrics.NewRegisteredMeter(namespace+"compact/time", nil) db.compTimeMeter = metrics.GetOrRegisterMeter(namespace+"compact/time", nil)
db.compReadMeter = metrics.NewRegisteredMeter(namespace+"compact/input", nil) db.compReadMeter = metrics.GetOrRegisterMeter(namespace+"compact/input", nil)
db.compWriteMeter = metrics.NewRegisteredMeter(namespace+"compact/output", nil) db.compWriteMeter = metrics.GetOrRegisterMeter(namespace+"compact/output", nil)
db.diskSizeGauge = metrics.NewRegisteredGauge(namespace+"disk/size", nil) db.diskSizeGauge = metrics.GetOrRegisterGauge(namespace+"disk/size", nil)
db.diskReadMeter = metrics.NewRegisteredMeter(namespace+"disk/read", nil) db.diskReadMeter = metrics.GetOrRegisterMeter(namespace+"disk/read", nil)
db.diskWriteMeter = metrics.NewRegisteredMeter(namespace+"disk/write", nil) db.diskWriteMeter = metrics.GetOrRegisterMeter(namespace+"disk/write", nil)
db.writeDelayMeter = metrics.NewRegisteredMeter(namespace+"compact/writedelay/duration", nil) db.writeDelayMeter = metrics.GetOrRegisterMeter(namespace+"compact/writedelay/duration", nil)
db.writeDelayNMeter = metrics.NewRegisteredMeter(namespace+"compact/writedelay/counter", nil) db.writeDelayNMeter = metrics.GetOrRegisterMeter(namespace+"compact/writedelay/counter", nil)
db.memCompGauge = metrics.NewRegisteredGauge(namespace+"compact/memory", nil) db.memCompGauge = metrics.GetOrRegisterGauge(namespace+"compact/memory", nil)
db.level0CompGauge = metrics.NewRegisteredGauge(namespace+"compact/level0", nil) db.level0CompGauge = metrics.GetOrRegisterGauge(namespace+"compact/level0", nil)
db.nonlevel0CompGauge = metrics.NewRegisteredGauge(namespace+"compact/nonlevel0", nil) db.nonlevel0CompGauge = metrics.GetOrRegisterGauge(namespace+"compact/nonlevel0", nil)
db.seekCompGauge = metrics.NewRegisteredGauge(namespace+"compact/seek", nil) db.seekCompGauge = metrics.GetOrRegisterGauge(namespace+"compact/seek", nil)
db.manualMemAllocGauge = metrics.NewRegisteredGauge(namespace+"memory/manualalloc", nil) db.manualMemAllocGauge = metrics.GetOrRegisterGauge(namespace+"memory/manualalloc", nil)
// Start up the metrics gathering and return // Start up the metrics gathering and return
go db.meter(metricsGatheringInterval, namespace) go db.meter(metricsGatheringInterval, namespace)
@@ -543,7 +543,7 @@ func (d *Database) meter(refresh time.Duration, namespace string) {
for i, level := range stats.Levels { for i, level := range stats.Levels {
// Append metrics for additional layers // Append metrics for additional layers
if i >= len(d.levelsGauge) { if i >= len(d.levelsGauge) {
d.levelsGauge = append(d.levelsGauge, metrics.NewRegisteredGauge(namespace+fmt.Sprintf("tables/level%v", i), nil)) d.levelsGauge = append(d.levelsGauge, metrics.GetOrRegisterGauge(namespace+fmt.Sprintf("tables/level%v", i), nil))
} }
d.levelsGauge[i].Update(level.NumFiles) d.levelsGauge[i].Update(level.NumFiles)
} }

View File

@@ -151,7 +151,7 @@ func (e *Era) GetBlockByNumber(num uint64) (*types.Block, error) {
if err := rlp.Decode(r, &body); err != nil { if err := rlp.Decode(r, &body); err != nil {
return nil, err return nil, err
} }
return types.NewBlockWithHeader(&header).WithBody(body.Transactions, body.Uncles), nil return types.NewBlockWithHeader(&header).WithBody(body), nil
} }
// Accumulator reads the accumulator entry in the Era1 file. // Accumulator reads the accumulator entry in the Era1 file.

View File

@@ -73,7 +73,7 @@ func (it *Iterator) Block() (*types.Block, error) {
if err := rlp.Decode(it.inner.Body, &body); err != nil { if err := rlp.Decode(it.inner.Body, &body); err != nil {
return nil, err return nil, err
} }
return types.NewBlockWithHeader(&header).WithBody(body.Transactions, body.Uncles), nil return types.NewBlockWithHeader(&header).WithBody(body), nil
} }
// Receipts returns the receipts for the iterator's current position. // Receipts returns the receipts for the iterator's current position.

View File

@@ -142,7 +142,7 @@ func (s *EthereumAPI) BlobBaseFee(ctx context.Context) *hexutil.Big {
} }
// Syncing returns false in case the node is currently not syncing with the network. It can be up-to-date or has not // Syncing returns false in case the node is currently not syncing with the network. It can be up-to-date or has not
// yet received the latest block headers from its pears. In case it is synchronizing: // yet received the latest block headers from its peers. In case it is synchronizing:
// - startingBlock: block number this node started to synchronize from // - startingBlock: block number this node started to synchronize from
// - currentBlock: block number this node is currently importing // - currentBlock: block number this node is currently importing
// - highestBlock: block number of the highest block header this node has received from peers // - highestBlock: block number of the highest block header this node has received from peers
@@ -1524,6 +1524,9 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
prevTracer = logger.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles) prevTracer = logger.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles)
} }
for { for {
if err := ctx.Err(); err != nil {
return nil, 0, nil, err
}
// Retrieve the current access list to expand // Retrieve the current access list to expand
accessList := prevTracer.AccessList() accessList := prevTracer.AccessList()
log.Trace("Creating access list", "input", accessList) log.Trace("Creating access list", "input", accessList)

View File

@@ -1348,7 +1348,7 @@ func TestRPCMarshalBlock(t *testing.T) {
} }
txs = append(txs, tx) txs = append(txs, tx)
} }
block := types.NewBlock(&types.Header{Number: big.NewInt(100)}, txs, nil, nil, blocktest.NewHasher()) block := types.NewBlock(&types.Header{Number: big.NewInt(100)}, &types.Body{Transactions: txs}, nil, blocktest.NewHasher())
var testSuite = []struct { var testSuite = []struct {
inclTx bool inclTx bool
@@ -1559,7 +1559,7 @@ func TestRPCGetBlockOrHeader(t *testing.T) {
Address: common.Address{0x12, 0x34}, Address: common.Address{0x12, 0x34},
Amount: 10, Amount: 10,
} }
pending = types.NewBlockWithWithdrawals(&types.Header{Number: big.NewInt(11), Time: 42}, []*types.Transaction{tx}, nil, nil, []*types.Withdrawal{withdrawal}, blocktest.NewHasher()) pending = types.NewBlock(&types.Header{Number: big.NewInt(11), Time: 42}, &types.Body{Transactions: types.Transactions{tx}, Withdrawals: types.Withdrawals{withdrawal}}, nil, blocktest.NewHasher())
) )
backend := newTestBackend(t, genBlocks, genesis, ethash.NewFaker(), func(i int, b *core.BlockGen) { backend := newTestBackend(t, genBlocks, genesis, ethash.NewFaker(), func(i int, b *core.BlockGen) {
// Transfer from account[0] to account[1] // Transfer from account[0] to account[1]

View File

@@ -78,7 +78,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header {
} }
func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
return types.NewBlock(bc.CurrentBlock(), nil, nil, nil, trie.NewStackTrie(nil)) return types.NewBlock(bc.CurrentBlock(), nil, nil, trie.NewStackTrie(nil))
} }
func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) {

View File

@@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
@@ -752,7 +753,7 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, ancient
var db ethdb.Database var db ethdb.Database
var err error var err error
if n.config.DataDir == "" { if n.config.DataDir == "" {
db = rawdb.NewMemoryDatabase() db, err = rawdb.NewDatabaseWithFreezer(memorydb.New(), "", namespace, readonly)
} else { } else {
db, err = rawdb.Open(rawdb.OpenOptions{ db, err = rawdb.Open(rawdb.OpenOptions{
Type: n.config.DBEngine, Type: n.config.DBEngine,

View File

@@ -548,7 +548,7 @@ func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error {
} }
packet := t.wrapPacket(rawpacket) packet := t.wrapPacket(rawpacket)
fromID := fromKey.ID() fromID := fromKey.ID()
if err == nil && packet.preverify != nil { if packet.preverify != nil {
err = packet.preverify(packet, from, fromID, fromKey) err = packet.preverify(packet, from, fromID, fromKey)
} }
t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", from, "err", err) t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", from, "err", err)

View File

@@ -30,6 +30,7 @@ import (
"testing" "testing"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@@ -283,9 +284,38 @@ func TestDecodeErrorsV5(t *testing.T) {
b = make([]byte, 63) b = make([]byte, 63)
net.nodeA.expectDecodeErr(t, errInvalidHeader, b) net.nodeA.expectDecodeErr(t, errInvalidHeader, b)
// TODO some more tests would be nice :) t.Run("invalid-handshake-datasize", func(t *testing.T) {
// - check invalid authdata sizes requiredNumber := 108
// - check invalid handshake data sizes
testDataFile := filepath.Join("testdata", "v5.1-ping-handshake"+".txt")
enc := hexFile(testDataFile)
//delete some byte from handshake to make it invalid
enc = enc[:len(enc)-requiredNumber]
net.nodeB.expectDecodeErr(t, errMsgTooShort, enc)
})
t.Run("invalid-auth-datasize", func(t *testing.T) {
testPacket := []byte{}
testDataFiles := []string{"v5.1-whoareyou", "v5.1-ping-handshake"}
for counter, name := range testDataFiles {
file := filepath.Join("testdata", name+".txt")
enc := hexFile(file)
if counter == 0 {
//make whoareyou header
testPacket = enc[:sizeofStaticPacketData-1]
testPacket = append(testPacket, 255)
}
if counter == 1 {
//append invalid auth size
testPacket = append(testPacket, enc[sizeofStaticPacketData:]...)
}
}
wantErr := "invalid auth size"
if _, err := net.nodeB.decode(testPacket); strings.HasSuffix(err.Error(), wantErr) {
t.Fatal(fmt.Errorf("(%s) got err %q, want %q", net.nodeB.ln.ID().TerminalString(), err, wantErr))
}
})
} }
// This test checks that all test vectors can be decoded. // This test checks that all test vectors can be decoded.

View File

@@ -20,6 +20,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"maps"
"math" "math"
"net" "net"
"sync" "sync"
@@ -215,10 +216,7 @@ func (sn *SimNode) ServeRPC(conn *websocket.Conn) error {
// simulation_snapshot RPC method // simulation_snapshot RPC method
func (sn *SimNode) Snapshots() (map[string][]byte, error) { func (sn *SimNode) Snapshots() (map[string][]byte, error) {
sn.lock.RLock() sn.lock.RLock()
services := make(map[string]node.Lifecycle, len(sn.running)) services := maps.Clone(sn.running)
for name, service := range sn.running {
services[name] = service
}
sn.lock.RUnlock() sn.lock.RUnlock()
if len(services) == 0 { if len(services) == 0 {
return nil, errors.New("no running services") return nil, errors.New("no running services")
@@ -315,11 +313,7 @@ func (sn *SimNode) Services() []node.Lifecycle {
func (sn *SimNode) ServiceMap() map[string]node.Lifecycle { func (sn *SimNode) ServiceMap() map[string]node.Lifecycle {
sn.lock.RLock() sn.lock.RLock()
defer sn.lock.RUnlock() defer sn.lock.RUnlock()
services := make(map[string]node.Lifecycle, len(sn.running)) return maps.Clone(sn.running)
for name, service := range sn.running {
services[name] = service
}
return services
} }
// Server returns the underlying p2p.Server // Server returns the underlying p2p.Server

View File

@@ -374,7 +374,7 @@ type ChainConfig struct {
type EthashConfig struct{} type EthashConfig struct{}
// String implements the stringer interface, returning the consensus engine details. // String implements the stringer interface, returning the consensus engine details.
func (c *EthashConfig) String() string { func (c EthashConfig) String() string {
return "ethash" return "ethash"
} }
@@ -385,8 +385,8 @@ type CliqueConfig struct {
} }
// String implements the stringer interface, returning the consensus engine details. // String implements the stringer interface, returning the consensus engine details.
func (c *CliqueConfig) String() string { func (c CliqueConfig) String() string {
return "clique" return fmt.Sprintf("clique(period: %d, epoch: %d)", c.Period, c.Epoch)
} }
// Description returns a human-readable description of ChainConfig. // Description returns a human-readable description of ChainConfig.
@@ -566,17 +566,17 @@ func (c *ChainConfig) IsShanghai(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.ShanghaiTime, time) return c.IsLondon(num) && isTimestampForked(c.ShanghaiTime, time)
} }
// IsCancun returns whether num is either equal to the Cancun fork time or greater. // IsCancun returns whether time is either equal to the Cancun fork time or greater.
func (c *ChainConfig) IsCancun(num *big.Int, time uint64) bool { func (c *ChainConfig) IsCancun(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.CancunTime, time) return c.IsLondon(num) && isTimestampForked(c.CancunTime, time)
} }
// IsPrague returns whether num is either equal to the Prague fork time or greater. // IsPrague returns whether time is either equal to the Prague fork time or greater.
func (c *ChainConfig) IsPrague(num *big.Int, time uint64) bool { func (c *ChainConfig) IsPrague(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.PragueTime, time) return c.IsLondon(num) && isTimestampForked(c.PragueTime, time)
} }
// IsVerkle returns whether num is either equal to the Verkle fork time or greater. // IsVerkle returns whether time is either equal to the Verkle fork time or greater.
func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool { func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time) return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time)
} }
@@ -880,7 +880,7 @@ func newTimestampCompatError(what string, storedtime, newtime *uint64) *ConfigCo
NewTime: newtime, NewTime: newtime,
RewindToTime: 0, RewindToTime: 0,
} }
if rew != nil { if rew != nil && *rew != 0 {
err.RewindToTime = *rew - 1 err.RewindToTime = *rew - 1
} }
return err return err
@@ -890,7 +890,15 @@ func (err *ConfigCompatError) Error() string {
if err.StoredBlock != nil { if err.StoredBlock != nil {
return fmt.Sprintf("mismatching %s in database (have block %d, want block %d, rewindto block %d)", err.What, err.StoredBlock, err.NewBlock, err.RewindToBlock) return fmt.Sprintf("mismatching %s in database (have block %d, want block %d, rewindto block %d)", err.What, err.StoredBlock, err.NewBlock, err.RewindToBlock)
} }
return fmt.Sprintf("mismatching %s in database (have timestamp %d, want timestamp %d, rewindto timestamp %d)", err.What, err.StoredTime, err.NewTime, err.RewindToTime)
if err.StoredTime == nil && err.NewTime == nil {
return ""
} else if err.StoredTime == nil && err.NewTime != nil {
return fmt.Sprintf("mismatching %s in database (have timestamp nil, want timestamp %d, rewindto timestamp %d)", err.What, *err.NewTime, err.RewindToTime)
} else if err.StoredTime != nil && err.NewTime == nil {
return fmt.Sprintf("mismatching %s in database (have timestamp %d, want timestamp nil, rewindto timestamp %d)", err.What, *err.StoredTime, err.RewindToTime)
}
return fmt.Sprintf("mismatching %s in database (have timestamp %d, want timestamp %d, rewindto timestamp %d)", err.What, *err.StoredTime, *err.NewTime, err.RewindToTime)
} }
// Rules wraps ChainConfig and is merely syntactic sugar or can be used for functions // Rules wraps ChainConfig and is merely syntactic sugar or can be used for functions

View File

@@ -23,6 +23,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/stretchr/testify/require"
) )
func TestCheckCompatible(t *testing.T) { func TestCheckCompatible(t *testing.T) {
@@ -137,3 +138,20 @@ func TestConfigRules(t *testing.T) {
t.Errorf("expected %v to be shanghai", stamp) t.Errorf("expected %v to be shanghai", stamp)
} }
} }
func TestTimestampCompatError(t *testing.T) {
require.Equal(t, new(ConfigCompatError).Error(), "")
errWhat := "Shanghai fork timestamp"
require.Equal(t, newTimestampCompatError(errWhat, nil, newUint64(1681338455)).Error(),
"mismatching Shanghai fork timestamp in database (have timestamp nil, want timestamp 1681338455, rewindto timestamp 1681338454)")
require.Equal(t, newTimestampCompatError(errWhat, newUint64(1681338455), nil).Error(),
"mismatching Shanghai fork timestamp in database (have timestamp 1681338455, want timestamp nil, rewindto timestamp 1681338454)")
require.Equal(t, newTimestampCompatError(errWhat, newUint64(1681338455), newUint64(600624000)).Error(),
"mismatching Shanghai fork timestamp in database (have timestamp 1681338455, want timestamp 600624000, rewindto timestamp 600623999)")
require.Equal(t, newTimestampCompatError(errWhat, newUint64(0), newUint64(1681338455)).Error(),
"mismatching Shanghai fork timestamp in database (have timestamp 0, want timestamp 1681338455, rewindto timestamp 0)")
}

View File

@@ -23,7 +23,7 @@ import (
const ( const (
VersionMajor = 1 // Major version component of the current release VersionMajor = 1 // Major version component of the current release
VersionMinor = 14 // Minor version component of the current release VersionMinor = 14 // Minor version component of the current release
VersionPatch = 0 // Patch version component of the current release VersionPatch = 3 // Patch version component of the current release
VersionMeta = "stable" // Version metadata to append to the version string VersionMeta = "stable" // Version metadata to append to the version string
) )

View File

@@ -64,14 +64,6 @@ func TestExecutionSpecBlocktests(t *testing.T) {
} }
bt := new(testMatcher) bt := new(testMatcher)
// These tests fail as of https://github.com/ethereum/go-ethereum/pull/28666, since we
// no longer delete "leftover storage" when deploying a contract.
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/self_destructing_initcode_create_tx.json`)
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/self_destructing_initcode.json`)
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/recreate_self_destructed_contract_different_txs.json`)
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/delegatecall_from_new_contract_to_pre_existing_contract.json`)
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/create_selfdestruct_same_tx.json`)
bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) { bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) {
execBlockTest(t, bt, test) execBlockTest(t, bt, test)
}) })

View File

@@ -21,7 +21,6 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/sha3"
) )
// hasher is a type used for the trie Hash operation. A hasher has some // hasher is a type used for the trie Hash operation. A hasher has some
@@ -38,7 +37,7 @@ var hasherPool = sync.Pool{
New: func() interface{} { New: func() interface{} {
return &hasher{ return &hasher{
tmp: make([]byte, 0, 550), // cap is as large as a full fullNode. tmp: make([]byte, 0, 550), // cap is as large as a full fullNode.
sha: sha3.NewLegacyKeccak256().(crypto.KeccakState), sha: crypto.NewKeccakState(),
encbuf: rlp.NewEncoderBuffer(nil), encbuf: rlp.NewEncoderBuffer(nil),
} }
}, },

View File

@@ -28,7 +28,6 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"golang.org/x/crypto/sha3"
) )
func FuzzStackTrie(f *testing.F) { func FuzzStackTrie(f *testing.F) {
@@ -41,10 +40,10 @@ func fuzz(data []byte, debugging bool) {
// This spongeDb is used to check the sequence of disk-db-writes // This spongeDb is used to check the sequence of disk-db-writes
var ( var (
input = bytes.NewReader(data) input = bytes.NewReader(data)
spongeA = &spongeDb{sponge: sha3.NewLegacyKeccak256()} spongeA = &spongeDb{sponge: crypto.NewKeccakState()}
dbA = newTestDatabase(rawdb.NewDatabase(spongeA), rawdb.HashScheme) dbA = newTestDatabase(rawdb.NewDatabase(spongeA), rawdb.HashScheme)
trieA = NewEmpty(dbA) trieA = NewEmpty(dbA)
spongeB = &spongeDb{sponge: sha3.NewLegacyKeccak256()} spongeB = &spongeDb{sponge: crypto.NewKeccakState()}
dbB = newTestDatabase(rawdb.NewDatabase(spongeB), rawdb.HashScheme) dbB = newTestDatabase(rawdb.NewDatabase(spongeB), rawdb.HashScheme)
trieB = NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { trieB = NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme()) rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme())

View File

@@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/common/prque" "github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
@@ -546,9 +547,9 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
// the performance impact negligible. // the performance impact negligible.
var exists bool var exists bool
if owner == (common.Hash{}) { if owner == (common.Hash{}) {
exists = rawdb.ExistsAccountTrieNode(s.database, append(inner, key[:i]...)) exists = rawdb.HasAccountTrieNode(s.database, append(inner, key[:i]...))
} else { } else {
exists = rawdb.ExistsStorageTrieNode(s.database, owner, append(inner, key[:i]...)) exists = rawdb.HasStorageTrieNode(s.database, owner, append(inner, key[:i]...))
} }
if exists { if exists {
s.membatch.delNode(owner, append(inner, key[:i]...)) s.membatch.delNode(owner, append(inner, key[:i]...))
@@ -691,13 +692,14 @@ func (s *Sync) hasNode(owner common.Hash, path []byte, hash common.Hash) (exists
} }
// If node is running with path scheme, check the presence with node path. // If node is running with path scheme, check the presence with node path.
var blob []byte var blob []byte
var dbHash common.Hash
if owner == (common.Hash{}) { if owner == (common.Hash{}) {
blob, dbHash = rawdb.ReadAccountTrieNode(s.database, path) blob = rawdb.ReadAccountTrieNode(s.database, path)
} else { } else {
blob, dbHash = rawdb.ReadStorageTrieNode(s.database, owner, path) blob = rawdb.ReadStorageTrieNode(s.database, owner, path)
} }
exists = hash == dbHash h := newBlobHasher()
defer h.release()
exists = hash == h.hash(blob)
inconsistent = !exists && len(blob) != 0 inconsistent = !exists && len(blob) != 0
return exists, inconsistent return exists, inconsistent
} }
@@ -712,3 +714,23 @@ func ResolvePath(path []byte) (common.Hash, []byte) {
} }
return owner, path return owner, path
} }
// blobHasher is used to compute the sha256 hash of the provided data.
type blobHasher struct{ state crypto.KeccakState }
// blobHasherPool is the pool for reusing pre-allocated hash state.
var blobHasherPool = sync.Pool{
New: func() interface{} { return &blobHasher{state: crypto.NewKeccakState()} },
}
func newBlobHasher() *blobHasher {
return blobHasherPool.Get().(*blobHasher)
}
func (h *blobHasher) hash(data []byte) common.Hash {
return crypto.HashData(h.state, data)
}
func (h *blobHasher) release() {
blobHasherPool.Put(h)
}

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