mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 18:32:23 +00:00
Merge branch 'ethereum:master' into master
This commit is contained in:
commit
83e1cacc40
160 changed files with 4213 additions and 3158 deletions
4
.github/workflows/go.yml
vendored
4
.github/workflows/go.yml
vendored
|
|
@ -11,9 +11,9 @@ jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.21.4
|
go-version: 1.21.4
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
|
|
||||||
56
.travis.yml
56
.travis.yml
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -98,48 +97,34 @@ jobs:
|
||||||
|
|
||||||
# These builders run the tests
|
# These builders run the tests
|
||||||
- stage: build
|
- stage: build
|
||||||
|
if: type = push
|
||||||
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
|
- stage: build
|
||||||
if: type = pull_request
|
if: type = push
|
||||||
os: linux
|
os: linux
|
||||||
arch: arm64
|
dist: noble
|
||||||
dist: bionic
|
|
||||||
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
|
||||||
|
|
||||||
- stage: build
|
|
||||||
os: linux
|
|
||||||
dist: bionic
|
|
||||||
go: 1.21.x
|
|
||||||
script:
|
|
||||||
- travis_wait 30 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 +133,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 +146,9 @@ 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:
|
||||||
|
- racetests
|
||||||
script:
|
script:
|
||||||
- travis_wait 30 go run build/ci.go test -race $TEST_PACKAGES
|
- travis_wait 60 go run build/ci.go test -race $TEST_PACKAGES
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ func TestValidatedHead(t *testing.T) {
|
||||||
ts.ServerEvent(EvNewOptimisticUpdate, testServer3, testOptUpdate4)
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer3, testOptUpdate4)
|
||||||
// finality should be requested from both servers
|
// finality should be requested from both servers
|
||||||
ts.Run(4, testServer1, ReqFinality{}, testServer3, ReqFinality{})
|
ts.Run(4, testServer1, ReqFinality{}, testServer3, ReqFinality{})
|
||||||
// future period annonced heads should be queued
|
// future period announced heads should be queued
|
||||||
ht.ExpValidated(t, 4, nil)
|
ht.ExpValidated(t, 4, nil)
|
||||||
|
|
||||||
chain.SetNextSyncPeriod(2)
|
chain.SetNextSyncPeriod(2)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
54
build/ci.go
54
build/ci.go
|
|
@ -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,7 +686,7 @@ 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
|
||||||
|
|
@ -708,18 +700,20 @@ 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
|
||||||
|
for i, gobootbundle := range gobootbundles {
|
||||||
if err := build.ExtractArchive(gobootbundle, pkgdir); err != nil {
|
if err := build.ExtractArchive(gobootbundle, pkgdir); err != nil {
|
||||||
log.Fatalf("Failed to extract bootstrapper Go sources: %v", err)
|
log.Fatalf("Failed to extract bootstrapper Go sources: %v", err)
|
||||||
}
|
}
|
||||||
if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".goboot")); err != nil {
|
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)
|
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 {
|
||||||
log.Fatalf("Failed to extract builder Go sources: %v", err)
|
log.Fatalf("Failed to extract builder Go sources: %v", err)
|
||||||
|
|
@ -754,11 +748,14 @@ 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")
|
|
||||||
|
var bundles []string
|
||||||
|
for _, booter := range []string{"ppa-builder-1", "ppa-builder-2"} {
|
||||||
|
gobootVersion, err := build.Version(csdb, booter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -768,7 +765,9 @@ func downloadGoBootstrapSources(cachedir string) string {
|
||||||
if err := csdb.DownloadFile(url, dst); err != nil {
|
if err := csdb.DownloadFile(url, dst); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return dst
|
bundles = append(bundles, dst)
|
||||||
|
}
|
||||||
|
return bundles
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadGoSources downloads the Go source tarball.
|
// downloadGoSources downloads the Go source tarball.
|
||||||
|
|
@ -847,9 +846,6 @@ 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,14 +873,12 @@ 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,
|
|
||||||
GoBootPath: debGoBootPaths[goboot],
|
|
||||||
PackageName: name,
|
PackageName: name,
|
||||||
Env: env,
|
Env: env,
|
||||||
Author: author,
|
Author: author,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,7 @@ func TestT8n(t *testing.T) {
|
||||||
{ // Test post-merge transition
|
{ // Test post-merge transition
|
||||||
base: "./testdata/24",
|
base: "./testdata/24",
|
||||||
input: t8nInput{
|
input: t8nInput{
|
||||||
"alloc.json", "txs.json", "env.json", "Merge", "",
|
"alloc.json", "txs.json", "env.json", "Paris", "",
|
||||||
},
|
},
|
||||||
output: t8nOutput{alloc: true, result: true},
|
output: t8nOutput{alloc: true, result: true},
|
||||||
expOut: "exp.json",
|
expOut: "exp.json",
|
||||||
|
|
@ -242,7 +242,7 @@ func TestT8n(t *testing.T) {
|
||||||
{ // Test post-merge transition where input is missing random
|
{ // Test post-merge transition where input is missing random
|
||||||
base: "./testdata/24",
|
base: "./testdata/24",
|
||||||
input: t8nInput{
|
input: t8nInput{
|
||||||
"alloc.json", "txs.json", "env-missingrandom.json", "Merge", "",
|
"alloc.json", "txs.json", "env-missingrandom.json", "Paris", "",
|
||||||
},
|
},
|
||||||
output: t8nOutput{alloc: false, result: false},
|
output: t8nOutput{alloc: false, result: false},
|
||||||
expExitCode: 3,
|
expExitCode: 3,
|
||||||
|
|
@ -250,7 +250,7 @@ func TestT8n(t *testing.T) {
|
||||||
{ // Test base fee calculation
|
{ // Test base fee calculation
|
||||||
base: "./testdata/25",
|
base: "./testdata/25",
|
||||||
input: t8nInput{
|
input: t8nInput{
|
||||||
"alloc.json", "txs.json", "env.json", "Merge", "",
|
"alloc.json", "txs.json", "env.json", "Paris", "",
|
||||||
},
|
},
|
||||||
output: t8nOutput{alloc: true, result: true},
|
output: t8nOutput{alloc: true, result: true},
|
||||||
expOut: "exp.json",
|
expOut: "exp.json",
|
||||||
|
|
@ -378,7 +378,7 @@ func TestT8nTracing(t *testing.T) {
|
||||||
{
|
{
|
||||||
base: "./testdata/32",
|
base: "./testdata/32",
|
||||||
input: t8nInput{
|
input: t8nInput{
|
||||||
"alloc.json", "txs.json", "env.json", "Merge", "",
|
"alloc.json", "txs.json", "env.json", "Paris", "",
|
||||||
},
|
},
|
||||||
extraArgs: []string{"--trace", "--trace.callframes"},
|
extraArgs: []string{"--trace", "--trace.callframes"},
|
||||||
expectedTraces: []string{"trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl"},
|
expectedTraces: []string{"trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl"},
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
|
||||||
utils.MetricsInfluxDBOrganizationFlag,
|
utils.MetricsInfluxDBOrganizationFlag,
|
||||||
utils.TxLookupLimitFlag,
|
utils.TxLookupLimitFlag,
|
||||||
utils.VMTraceFlag,
|
utils.VMTraceFlag,
|
||||||
utils.VMTraceConfigFlag,
|
utils.VMTraceJsonConfigFlag,
|
||||||
utils.TransactionHistoryFlag,
|
utils.TransactionHistoryFlag,
|
||||||
utils.StateHistoryFlag,
|
utils.StateHistoryFlag,
|
||||||
}, utils.DatabaseFlags),
|
}, utils.DatabaseFlags),
|
||||||
|
|
|
||||||
|
|
@ -103,17 +103,17 @@ func TestAttachWelcome(t *testing.T) {
|
||||||
"--http", "--http.port", httpPort,
|
"--http", "--http.port", httpPort,
|
||||||
"--ws", "--ws.port", wsPort)
|
"--ws", "--ws.port", wsPort)
|
||||||
t.Run("ipc", func(t *testing.T) {
|
t.Run("ipc", func(t *testing.T) {
|
||||||
waitForEndpoint(t, ipc, 3*time.Second)
|
waitForEndpoint(t, ipc, 4*time.Second)
|
||||||
testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs)
|
testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs)
|
||||||
})
|
})
|
||||||
t.Run("http", func(t *testing.T) {
|
t.Run("http", func(t *testing.T) {
|
||||||
endpoint := "http://127.0.0.1:" + httpPort
|
endpoint := "http://127.0.0.1:" + httpPort
|
||||||
waitForEndpoint(t, endpoint, 3*time.Second)
|
waitForEndpoint(t, endpoint, 4*time.Second)
|
||||||
testAttachWelcome(t, geth, endpoint, httpAPIs)
|
testAttachWelcome(t, geth, endpoint, httpAPIs)
|
||||||
})
|
})
|
||||||
t.Run("ws", func(t *testing.T) {
|
t.Run("ws", func(t *testing.T) {
|
||||||
endpoint := "ws://127.0.0.1:" + wsPort
|
endpoint := "ws://127.0.0.1:" + wsPort
|
||||||
waitForEndpoint(t, endpoint, 3*time.Second)
|
waitForEndpoint(t, endpoint, 4*time.Second)
|
||||||
testAttachWelcome(t, geth, endpoint, httpAPIs)
|
testAttachWelcome(t, geth, endpoint, httpAPIs)
|
||||||
})
|
})
|
||||||
geth.Kill()
|
geth.Kill()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ var (
|
||||||
utils.DeveloperPeriodFlag,
|
utils.DeveloperPeriodFlag,
|
||||||
utils.VMEnableDebugFlag,
|
utils.VMEnableDebugFlag,
|
||||||
utils.VMTraceFlag,
|
utils.VMTraceFlag,
|
||||||
utils.VMTraceConfigFlag,
|
utils.VMTraceJsonConfigFlag,
|
||||||
utils.NetworkIdFlag,
|
utils.NetworkIdFlag,
|
||||||
utils.EthStatsURLFlag,
|
utils.EthStatsURLFlag,
|
||||||
utils.NoCompactionFlag,
|
utils.NoCompactionFlag,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/internal/flags"
|
"github.com/ethereum/go-ethereum/internal/flags"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/gballet/go-verkle"
|
"github.com/ethereum/go-verkle"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -544,7 +544,7 @@ var (
|
||||||
Usage: "Name of tracer which should record internal VM operations (costly)",
|
Usage: "Name of tracer which should record internal VM operations (costly)",
|
||||||
Category: flags.VMCategory,
|
Category: flags.VMCategory,
|
||||||
}
|
}
|
||||||
VMTraceConfigFlag = &cli.StringFlag{
|
VMTraceJsonConfigFlag = &cli.StringFlag{
|
||||||
Name: "vmtrace.jsonconfig",
|
Name: "vmtrace.jsonconfig",
|
||||||
Usage: "Tracer configuration (JSON)",
|
Usage: "Tracer configuration (JSON)",
|
||||||
Category: flags.VMCategory,
|
Category: flags.VMCategory,
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -1903,12 +1905,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||||
if ctx.IsSet(VMTraceFlag.Name) {
|
if ctx.IsSet(VMTraceFlag.Name) {
|
||||||
if name := ctx.String(VMTraceFlag.Name); name != "" {
|
if name := ctx.String(VMTraceFlag.Name); name != "" {
|
||||||
var config string
|
var config string
|
||||||
if ctx.IsSet(VMTraceConfigFlag.Name) {
|
if ctx.IsSet(VMTraceJsonConfigFlag.Name) {
|
||||||
config = ctx.String(VMTraceConfigFlag.Name)
|
config = ctx.String(VMTraceJsonConfigFlag.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.VMTrace = name
|
cfg.VMTrace = name
|
||||||
cfg.VMTraceConfig = config
|
cfg.VMTraceJsonConfig = config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2192,8 +2194,8 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
||||||
if ctx.IsSet(VMTraceFlag.Name) {
|
if ctx.IsSet(VMTraceFlag.Name) {
|
||||||
if name := ctx.String(VMTraceFlag.Name); name != "" {
|
if name := ctx.String(VMTraceFlag.Name); name != "" {
|
||||||
var config json.RawMessage
|
var config json.RawMessage
|
||||||
if ctx.IsSet(VMTraceConfigFlag.Name) {
|
if ctx.IsSet(VMTraceJsonConfigFlag.Name) {
|
||||||
config = json.RawMessage(ctx.String(VMTraceConfigFlag.Name))
|
config = json.RawMessage(ctx.String(VMTraceJsonConfigFlag.Name))
|
||||||
}
|
}
|
||||||
t, err := tracers.LiveDirectory.New(name, config)
|
t, err := tracers.LiveDirectory.New(name, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
@ -1153,6 +1151,10 @@ func (bc *BlockChain) Stop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Allow tracers to clean-up and release resources.
|
||||||
|
if bc.logger != nil && bc.logger.OnClose != nil {
|
||||||
|
bc.logger.OnClose()
|
||||||
|
}
|
||||||
// Close the trie database, release all the held resources as the last step.
|
// Close the trie database, release all the held resources as the last step.
|
||||||
if err := bc.triedb.Close(); err != nil {
|
if err := bc.triedb.Close(); err != nil {
|
||||||
log.Error("Failed to close trie database", "err", err)
|
log.Error("Failed to close trie database", "err", err)
|
||||||
|
|
@ -1306,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{}{}
|
||||||
|
|
@ -1449,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 {
|
||||||
|
|
@ -1466,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
|
||||||
}
|
}
|
||||||
|
|
@ -1490,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
|
||||||
|
|
@ -1502,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 {
|
||||||
|
|
@ -1514,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)
|
||||||
|
|
@ -1803,8 +1805,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
}
|
}
|
||||||
statedb.SetLogger(bc.logger)
|
statedb.SetLogger(bc.logger)
|
||||||
|
|
||||||
// Enable prefetching to pull in trie node paths while processing transactions
|
// If we are past Byzantium, enable prefetching to pull in trie node paths
|
||||||
|
// while processing transactions. Before Byzantium the prefetcher is mostly
|
||||||
|
// useless due to the intermediate root hashing after each transaction.
|
||||||
|
if bc.chainConfig.IsByzantium(block.Number()) {
|
||||||
statedb.StartPrefetcher("chain")
|
statedb.StartPrefetcher("chain")
|
||||||
|
}
|
||||||
activeState = statedb
|
activeState = statedb
|
||||||
|
|
||||||
// If we have a followup block, run that against the current state to pre-cache
|
// If we have a followup block, run that against the current state to pre-cache
|
||||||
|
|
@ -1933,8 +1939,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
|
||||||
|
|
@ -1961,7 +1966,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
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
@ -3636,16 +3636,17 @@ func testSetCanonical(t *testing.T, scheme string) {
|
||||||
}
|
}
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
"github.com/gballet/go-verkle"
|
"github.com/ethereum/go-verkle"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,12 +43,11 @@ func TestGeneratePOSChain(t *testing.T) {
|
||||||
bb = common.Address{0xbb}
|
bb = common.Address{0xbb}
|
||||||
funds = big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(params.Ether))
|
funds = big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(params.Ether))
|
||||||
config = *params.AllEthashProtocolChanges
|
config = *params.AllEthashProtocolChanges
|
||||||
asm4788 = common.Hex2Bytes("3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500")
|
|
||||||
gspec = &Genesis{
|
gspec = &Genesis{
|
||||||
Config: &config,
|
Config: &config,
|
||||||
Alloc: types.GenesisAlloc{
|
Alloc: types.GenesisAlloc{
|
||||||
address: {Balance: funds},
|
address: {Balance: funds},
|
||||||
params.BeaconRootsAddress: {Balance: common.Big0, Code: asm4788},
|
params.BeaconRootsAddress: {Code: params.BeaconRootsCode},
|
||||||
},
|
},
|
||||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||||
Difficulty: common.Big1,
|
Difficulty: common.Big1,
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,11 @@ var (
|
||||||
// than init code size limit.
|
// than init code size limit.
|
||||||
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
|
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
|
||||||
|
|
||||||
|
// ErrInsufficientBalanceWitness is returned if the transaction sender has enough
|
||||||
|
// funds to cover the transfer, but not enough to pay for witness access/modification
|
||||||
|
// costs for the transaction
|
||||||
|
ErrInsufficientBalanceWitness = errors.New("insufficient funds to cover witness access costs for transaction")
|
||||||
|
|
||||||
// ErrInsufficientFunds is returned if the total cost of executing a transaction
|
// ErrInsufficientFunds is returned if the total cost of executing a transaction
|
||||||
// is higher than the balance of the user's account.
|
// is higher than the balance of the user's account.
|
||||||
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
|
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -593,6 +593,8 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
|
||||||
common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
|
common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
|
||||||
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
|
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
|
||||||
common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
|
common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
|
||||||
|
// Pre-deploy EIP-4788 system contract
|
||||||
|
params.BeaconRootsAddress: types.Account{Nonce: 1, Code: params.BeaconRootsCode},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if faucet != nil {
|
if faucet != nil {
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
325
core/rawdb/ancienttest/testsuite.go
Normal file
325
core/rawdb/ancienttest/testsuite.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -39,24 +39,38 @@ 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 {
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
428
core/rawdb/freezer_memory.go
Normal file
428
core/rawdb/freezer_memory.go
Normal 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
|
||||||
|
}
|
||||||
41
core/rawdb/freezer_memory_test.go
Normal file
41
core/rawdb/freezer_memory_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
320
core/state/access_events.go
Normal file
320
core/state/access_events.go
Normal file
|
|
@ -0,0 +1,320 @@
|
||||||
|
// 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 state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"maps"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/trie/utils"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mode specifies how a tree location has been accessed
|
||||||
|
// for the byte value:
|
||||||
|
// * the first bit is set if the branch has been edited
|
||||||
|
// * the second bit is set if the branch has been read
|
||||||
|
type mode byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
AccessWitnessReadFlag = mode(1)
|
||||||
|
AccessWitnessWriteFlag = mode(2)
|
||||||
|
)
|
||||||
|
|
||||||
|
var zeroTreeIndex uint256.Int
|
||||||
|
|
||||||
|
// AccessEvents lists the locations of the state that are being accessed
|
||||||
|
// during the production of a block.
|
||||||
|
type AccessEvents struct {
|
||||||
|
branches map[branchAccessKey]mode
|
||||||
|
chunks map[chunkAccessKey]mode
|
||||||
|
|
||||||
|
pointCache *utils.PointCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccessEvents(pointCache *utils.PointCache) *AccessEvents {
|
||||||
|
return &AccessEvents{
|
||||||
|
branches: make(map[branchAccessKey]mode),
|
||||||
|
chunks: make(map[chunkAccessKey]mode),
|
||||||
|
pointCache: pointCache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge is used to merge the access events that were generated during the
|
||||||
|
// execution of a tx, with the accumulation of all access events that were
|
||||||
|
// generated during the execution of all txs preceding this one in a block.
|
||||||
|
func (ae *AccessEvents) Merge(other *AccessEvents) {
|
||||||
|
for k := range other.branches {
|
||||||
|
ae.branches[k] |= other.branches[k]
|
||||||
|
}
|
||||||
|
for k, chunk := range other.chunks {
|
||||||
|
ae.chunks[k] |= chunk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys returns, predictably, the list of keys that were touched during the
|
||||||
|
// buildup of the access witness.
|
||||||
|
func (ae *AccessEvents) Keys() [][]byte {
|
||||||
|
// TODO: consider if parallelizing this is worth it, probably depending on len(ae.chunks).
|
||||||
|
keys := make([][]byte, 0, len(ae.chunks))
|
||||||
|
for chunk := range ae.chunks {
|
||||||
|
basePoint := ae.pointCache.Get(chunk.addr[:])
|
||||||
|
key := utils.GetTreeKeyWithEvaluatedAddress(basePoint, &chunk.treeIndex, chunk.leafKey)
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ae *AccessEvents) Copy() *AccessEvents {
|
||||||
|
cpy := &AccessEvents{
|
||||||
|
branches: maps.Clone(ae.branches),
|
||||||
|
chunks: maps.Clone(ae.chunks),
|
||||||
|
pointCache: ae.pointCache,
|
||||||
|
}
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAccount returns the gas to be charged for each of the currently cold
|
||||||
|
// member fields of an account.
|
||||||
|
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 {
|
||||||
|
var gas uint64
|
||||||
|
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite)
|
||||||
|
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite)
|
||||||
|
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
|
||||||
|
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite)
|
||||||
|
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
|
||||||
|
return gas
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageCallGas returns the gas to be charged for each of the currently
|
||||||
|
// cold member fields of an account, that need to be touched when making a message
|
||||||
|
// call to that account.
|
||||||
|
func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 {
|
||||||
|
var gas uint64
|
||||||
|
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false)
|
||||||
|
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false)
|
||||||
|
return gas
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueTransferGas returns the gas to be charged for each of the currently
|
||||||
|
// cold balance member fields of the caller and the callee accounts.
|
||||||
|
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 {
|
||||||
|
var gas uint64
|
||||||
|
gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
|
||||||
|
gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
|
||||||
|
return gas
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContractCreateInitGas returns the access gas costs for the initialization of
|
||||||
|
// a contract creation.
|
||||||
|
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, createSendsValue bool) uint64 {
|
||||||
|
var gas uint64
|
||||||
|
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true)
|
||||||
|
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true)
|
||||||
|
if createSendsValue {
|
||||||
|
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true)
|
||||||
|
}
|
||||||
|
return gas
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTxOrigin adds the member fields of the sender account to the access event list,
|
||||||
|
// so that cold accesses are not charged, since they are covered by the 21000 gas.
|
||||||
|
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) {
|
||||||
|
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.VersionLeafKey, false)
|
||||||
|
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
|
||||||
|
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.NonceLeafKey, true)
|
||||||
|
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
|
||||||
|
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTxDestination adds the member fields of the sender account to the access event list,
|
||||||
|
// so that cold accesses are not charged, since they are covered by the 21000 gas.
|
||||||
|
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) {
|
||||||
|
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false)
|
||||||
|
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, sendsValue)
|
||||||
|
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, false)
|
||||||
|
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
|
||||||
|
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SlotGas returns the amount of gas to be charged for a cold storage access.
|
||||||
|
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool) uint64 {
|
||||||
|
treeIndex, subIndex := utils.StorageIndex(slot.Bytes())
|
||||||
|
return ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the cold
|
||||||
|
// access cost to be charged, if need be.
|
||||||
|
func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 {
|
||||||
|
stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := ae.touchAddress(addr, treeIndex, subIndex, isWrite)
|
||||||
|
|
||||||
|
var gas uint64
|
||||||
|
if stemRead {
|
||||||
|
gas += params.WitnessBranchReadCost
|
||||||
|
}
|
||||||
|
if selectorRead {
|
||||||
|
gas += params.WitnessChunkReadCost
|
||||||
|
}
|
||||||
|
if stemWrite {
|
||||||
|
gas += params.WitnessBranchWriteCost
|
||||||
|
}
|
||||||
|
if selectorWrite {
|
||||||
|
gas += params.WitnessChunkWriteCost
|
||||||
|
}
|
||||||
|
if selectorFill {
|
||||||
|
gas += params.WitnessChunkFillCost
|
||||||
|
}
|
||||||
|
return gas
|
||||||
|
}
|
||||||
|
|
||||||
|
// touchAddress adds any missing access event to the access event list.
|
||||||
|
func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) {
|
||||||
|
branchKey := newBranchAccessKey(addr, treeIndex)
|
||||||
|
chunkKey := newChunkAccessKey(branchKey, subIndex)
|
||||||
|
|
||||||
|
// Read access.
|
||||||
|
var branchRead, chunkRead bool
|
||||||
|
if _, hasStem := ae.branches[branchKey]; !hasStem {
|
||||||
|
branchRead = true
|
||||||
|
ae.branches[branchKey] = AccessWitnessReadFlag
|
||||||
|
}
|
||||||
|
if _, hasSelector := ae.chunks[chunkKey]; !hasSelector {
|
||||||
|
chunkRead = true
|
||||||
|
ae.chunks[chunkKey] = AccessWitnessReadFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write access.
|
||||||
|
var branchWrite, chunkWrite, chunkFill bool
|
||||||
|
if isWrite {
|
||||||
|
if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 {
|
||||||
|
branchWrite = true
|
||||||
|
ae.branches[branchKey] |= AccessWitnessWriteFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
chunkValue := ae.chunks[chunkKey]
|
||||||
|
if (chunkValue & AccessWitnessWriteFlag) == 0 {
|
||||||
|
chunkWrite = true
|
||||||
|
ae.chunks[chunkKey] |= AccessWitnessWriteFlag
|
||||||
|
}
|
||||||
|
// TODO: charge chunk filling costs if the leaf was previously empty in the state
|
||||||
|
}
|
||||||
|
return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill
|
||||||
|
}
|
||||||
|
|
||||||
|
type branchAccessKey struct {
|
||||||
|
addr common.Address
|
||||||
|
treeIndex uint256.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBranchAccessKey(addr common.Address, treeIndex uint256.Int) branchAccessKey {
|
||||||
|
var sk branchAccessKey
|
||||||
|
sk.addr = addr
|
||||||
|
sk.treeIndex = treeIndex
|
||||||
|
return sk
|
||||||
|
}
|
||||||
|
|
||||||
|
type chunkAccessKey struct {
|
||||||
|
branchAccessKey
|
||||||
|
leafKey byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey {
|
||||||
|
var lk chunkAccessKey
|
||||||
|
lk.branchAccessKey = branchKey
|
||||||
|
lk.leafKey = leafKey
|
||||||
|
return lk
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs
|
||||||
|
func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool) uint64 {
|
||||||
|
// note that in the case where the copied code is outside the range of the
|
||||||
|
// contract code but touches the last leaf with contract code in it,
|
||||||
|
// we don't include the last leaf of code in the AccessWitness. The
|
||||||
|
// reason that we do not need the last leaf is the account's code size
|
||||||
|
// is already in the AccessWitness so a stateless verifier can see that
|
||||||
|
// the code from the last leaf is not needed.
|
||||||
|
if (codeLen == 0 && size == 0) || startPC > codeLen {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
endPC := startPC + size
|
||||||
|
if endPC > codeLen {
|
||||||
|
endPC = codeLen
|
||||||
|
}
|
||||||
|
if endPC > 0 {
|
||||||
|
endPC -= 1 // endPC is the last bytecode that will be touched.
|
||||||
|
}
|
||||||
|
|
||||||
|
var statelessGasCharged uint64
|
||||||
|
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
|
||||||
|
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
|
||||||
|
subIndex := byte((chunkNumber + 128) % 256)
|
||||||
|
gas := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite)
|
||||||
|
var overflow bool
|
||||||
|
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas)
|
||||||
|
if overflow {
|
||||||
|
panic("overflow when adding gas")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return statelessGasCharged
|
||||||
|
}
|
||||||
|
|
||||||
|
// VersionGas adds the account's version to the accessed data, and returns the
|
||||||
|
// amount of gas that it costs.
|
||||||
|
// Note that an access in write mode implies an access in read mode, whereas an
|
||||||
|
// access in read mode does not imply an access in write mode.
|
||||||
|
func (ae *AccessEvents) VersionGas(addr common.Address, isWrite bool) uint64 {
|
||||||
|
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BalanceGas adds the account's balance to the accessed data, and returns the
|
||||||
|
// amount of gas that it costs.
|
||||||
|
// in write mode. If false, the charged gas corresponds to an access in read mode.
|
||||||
|
// Note that an access in write mode implies an access in read mode, whereas an access in
|
||||||
|
// read mode does not imply an access in write mode.
|
||||||
|
func (ae *AccessEvents) BalanceGas(addr common.Address, isWrite bool) uint64 {
|
||||||
|
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NonceGas adds the account's nonce to the accessed data, and returns the
|
||||||
|
// amount of gas that it costs.
|
||||||
|
// in write mode. If false, the charged gas corresponds to an access in read mode.
|
||||||
|
// Note that an access in write mode implies an access in read mode, whereas an access in
|
||||||
|
// read mode does not imply an access in write mode.
|
||||||
|
func (ae *AccessEvents) NonceGas(addr common.Address, isWrite bool) uint64 {
|
||||||
|
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodeSizeGas adds the account's code size to the accessed data, and returns the
|
||||||
|
// amount of gas that it costs.
|
||||||
|
// in write mode. If false, the charged gas corresponds to an access in read mode.
|
||||||
|
// Note that an access in write mode implies an access in read mode, whereas an access in
|
||||||
|
// read mode does not imply an access in write mode.
|
||||||
|
func (ae *AccessEvents) CodeSizeGas(addr common.Address, isWrite bool) uint64 {
|
||||||
|
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodeHashGas adds the account's code hash to the accessed data, and returns the
|
||||||
|
// amount of gas that it costs.
|
||||||
|
// in write mode. If false, the charged gas corresponds to an access in read mode.
|
||||||
|
// Note that an access in write mode implies an access in read mode, whereas an access in
|
||||||
|
// read mode does not imply an access in write mode.
|
||||||
|
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 {
|
||||||
|
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite)
|
||||||
|
}
|
||||||
153
core/state/access_events_test.go
Normal file
153
core/state/access_events_test.go
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
// 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 state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/trie/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testAddr [20]byte
|
||||||
|
testAddr2 [20]byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for i := byte(0); i < 20; i++ {
|
||||||
|
testAddr[i] = i
|
||||||
|
testAddr[2] = 2 * i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountHeaderGas(t *testing.T) {
|
||||||
|
ae := NewAccessEvents(utils.NewPointCache(1024))
|
||||||
|
|
||||||
|
// Check cold read cost
|
||||||
|
gas := ae.VersionGas(testAddr, false)
|
||||||
|
if gas != params.WitnessBranchReadCost+params.WitnessChunkReadCost {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessChunkReadCost)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check warm read cost
|
||||||
|
gas = ae.VersionGas(testAddr, false)
|
||||||
|
if gas != 0 {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cold read costs in the same group no longer incur the branch read cost
|
||||||
|
gas = ae.BalanceGas(testAddr, false)
|
||||||
|
if gas != params.WitnessChunkReadCost {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
|
||||||
|
}
|
||||||
|
gas = ae.NonceGas(testAddr, false)
|
||||||
|
if gas != params.WitnessChunkReadCost {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
|
||||||
|
}
|
||||||
|
gas = ae.CodeSizeGas(testAddr, false)
|
||||||
|
if gas != params.WitnessChunkReadCost {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
|
||||||
|
}
|
||||||
|
gas = ae.CodeHashGas(testAddr, false)
|
||||||
|
if gas != params.WitnessChunkReadCost {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cold write cost
|
||||||
|
gas = ae.VersionGas(testAddr, true)
|
||||||
|
if gas != params.WitnessBranchWriteCost+params.WitnessChunkWriteCost {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessBranchWriteCost)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check warm write cost
|
||||||
|
gas = ae.VersionGas(testAddr, true)
|
||||||
|
if gas != 0 {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check a write without a read charges both read and write costs
|
||||||
|
gas = ae.BalanceGas(testAddr2, true)
|
||||||
|
if gas != params.WitnessBranchReadCost+params.WitnessBranchWriteCost+params.WitnessChunkWriteCost+params.WitnessChunkReadCost {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessBranchWriteCost+params.WitnessChunkWriteCost+params.WitnessChunkReadCost)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that a write followed by a read charges nothing
|
||||||
|
gas = ae.BalanceGas(testAddr2, false)
|
||||||
|
if gas != 0 {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that reading a slot from the account header only charges the
|
||||||
|
// chunk read cost.
|
||||||
|
gas = ae.SlotGas(testAddr, common.Hash{}, false)
|
||||||
|
if gas != params.WitnessChunkReadCost {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestContractCreateInitGas checks that the gas cost of contract creation is correctly
|
||||||
|
// calculated.
|
||||||
|
func TestContractCreateInitGas(t *testing.T) {
|
||||||
|
ae := NewAccessEvents(utils.NewPointCache(1024))
|
||||||
|
|
||||||
|
var testAddr [20]byte
|
||||||
|
for i := byte(0); i < 20; i++ {
|
||||||
|
testAddr[i] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cold read cost, without a value
|
||||||
|
gas := ae.ContractCreateInitGas(testAddr, false)
|
||||||
|
if gas != params.WitnessBranchWriteCost+params.WitnessBranchReadCost+params.WitnessChunkWriteCost*2+params.WitnessChunkReadCost*2 {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchWriteCost+params.WitnessBranchReadCost+params.WitnessChunkWriteCost*3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check warm read cost
|
||||||
|
gas = ae.ContractCreateInitGas(testAddr, false)
|
||||||
|
if gas != 0 {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMessageCallGas checks that the gas cost of message calls is correctly
|
||||||
|
// calculated.
|
||||||
|
func TestMessageCallGas(t *testing.T) {
|
||||||
|
ae := NewAccessEvents(utils.NewPointCache(1024))
|
||||||
|
|
||||||
|
// Check cold read cost, without a value
|
||||||
|
gas := ae.MessageCallGas(testAddr)
|
||||||
|
if gas != params.WitnessBranchReadCost+params.WitnessChunkReadCost*2 {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessChunkReadCost*2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that reading the version and code size of the same account does not incur the branch read cost
|
||||||
|
gas = ae.VersionGas(testAddr, false)
|
||||||
|
if gas != 0 {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||||
|
}
|
||||||
|
gas = ae.CodeSizeGas(testAddr, false)
|
||||||
|
if gas != 0 {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check warm read cost
|
||||||
|
gas = ae.MessageCallGas(testAddr)
|
||||||
|
if gas != 0 {
|
||||||
|
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/crate-crypto/go-ipa/banderwagon"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/lru"
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
|
@ -40,11 +39,8 @@ const (
|
||||||
// Cache size granted for caching clean code.
|
// Cache size granted for caching clean code.
|
||||||
codeCacheSize = 64 * 1024 * 1024
|
codeCacheSize = 64 * 1024 * 1024
|
||||||
|
|
||||||
// commitmentSize is the size of commitment stored in cache.
|
// Number of address->curve point associations to keep.
|
||||||
commitmentSize = banderwagon.UncompressedSize
|
pointCacheSize = 4096
|
||||||
|
|
||||||
// Cache item granted for caching commitment results.
|
|
||||||
commitmentCacheItems = 64 * 1024 * 1024 / (commitmentSize + common.AddressLength)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Database wraps access to tries and contract code.
|
// Database wraps access to tries and contract code.
|
||||||
|
|
@ -67,6 +63,9 @@ type Database interface {
|
||||||
// DiskDB returns the underlying key-value disk database.
|
// DiskDB returns the underlying key-value disk database.
|
||||||
DiskDB() ethdb.KeyValueStore
|
DiskDB() ethdb.KeyValueStore
|
||||||
|
|
||||||
|
// PointCache returns the cache holding points used in verkle tree key computation
|
||||||
|
PointCache() *utils.PointCache
|
||||||
|
|
||||||
// TrieDB returns the underlying trie database for managing trie nodes.
|
// TrieDB returns the underlying trie database for managing trie nodes.
|
||||||
TrieDB() *triedb.Database
|
TrieDB() *triedb.Database
|
||||||
}
|
}
|
||||||
|
|
@ -139,6 +138,9 @@ type Trie interface {
|
||||||
// nodes of the longest existing prefix of the key (at least the root), ending
|
// nodes of the longest existing prefix of the key (at least the root), ending
|
||||||
// with the node that proves the absence of the key.
|
// with the node that proves the absence of the key.
|
||||||
Prove(key []byte, proofDb ethdb.KeyValueWriter) error
|
Prove(key []byte, proofDb ethdb.KeyValueWriter) error
|
||||||
|
|
||||||
|
// IsVerkle returns true if the trie is verkle-tree based
|
||||||
|
IsVerkle() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDatabase creates a backing store for state. The returned database is safe for
|
// NewDatabase creates a backing store for state. The returned database is safe for
|
||||||
|
|
@ -157,6 +159,7 @@ func NewDatabaseWithConfig(db ethdb.Database, config *triedb.Config) Database {
|
||||||
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
||||||
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
||||||
triedb: triedb.NewDatabase(db, config),
|
triedb: triedb.NewDatabase(db, config),
|
||||||
|
pointCache: utils.NewPointCache(pointCacheSize),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,6 +170,7 @@ func NewDatabaseWithNodeDB(db ethdb.Database, triedb *triedb.Database) Database
|
||||||
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
||||||
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
||||||
triedb: triedb,
|
triedb: triedb,
|
||||||
|
pointCache: utils.NewPointCache(pointCacheSize),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,12 +179,13 @@ type cachingDB struct {
|
||||||
codeSizeCache *lru.Cache[common.Hash, int]
|
codeSizeCache *lru.Cache[common.Hash, int]
|
||||||
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
|
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
|
||||||
triedb *triedb.Database
|
triedb *triedb.Database
|
||||||
|
pointCache *utils.PointCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenTrie opens the main account trie at a specific root hash.
|
// OpenTrie opens the main account trie at a specific root hash.
|
||||||
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
|
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
|
||||||
if db.triedb.IsVerkle() {
|
if db.triedb.IsVerkle() {
|
||||||
return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(commitmentCacheItems))
|
return trie.NewVerkleTrie(root, db.triedb, db.pointCache)
|
||||||
}
|
}
|
||||||
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
|
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -266,3 +271,8 @@ func (db *cachingDB) DiskDB() ethdb.KeyValueStore {
|
||||||
func (db *cachingDB) TrieDB() *triedb.Database {
|
func (db *cachingDB) TrieDB() *triedb.Database {
|
||||||
return db.triedb
|
return db.triedb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PointCache returns the cache of evaluated curve points.
|
||||||
|
func (db *cachingDB) PointCache() *utils.PointCache {
|
||||||
|
return db.pointCache
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -116,7 +130,9 @@ type (
|
||||||
}
|
}
|
||||||
storageChange struct {
|
storageChange struct {
|
||||||
account *common.Address
|
account *common.Address
|
||||||
key, prevalue common.Hash
|
key common.Hash
|
||||||
|
prevvalue common.Hash
|
||||||
|
origvalue common.Hash
|
||||||
}
|
}
|
||||||
codeChange struct {
|
codeChange struct {
|
||||||
account *common.Address
|
account *common.Address
|
||||||
|
|
@ -136,6 +152,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 +162,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 +171,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 +209,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 +226,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 +240,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 +255,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 +270,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, ch.origvalue)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +302,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 +318,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 +338,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 +352,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 +375,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 +388,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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,32 +21,29 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"maps"
|
"maps"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"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
|
// hasherPool holds a pool of hashers used by state objects during concurrent
|
||||||
|
// trie updates.
|
||||||
func (c Code) String() string {
|
var hasherPool = sync.Pool{
|
||||||
return string(c) //strings.Join(Disassemble(c), " ")
|
New: func() interface{} {
|
||||||
|
return crypto.NewKeccakState()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +63,7 @@ type stateObject struct {
|
||||||
|
|
||||||
// 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 +72,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 +91,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 +104,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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,16 +127,12 @@ func (s *stateObject) touch() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTrie returns the associated storage trie. The trie will be opened
|
// getTrie returns the associated storage trie. The trie will be opened if it'
|
||||||
// if it's not loaded previously. An error will be returned if trie can't
|
// not loaded previously. An error will be returned if trie can't be loaded.
|
||||||
// be loaded.
|
//
|
||||||
|
// If a new trie is opened, it will be cached within the state object to allow
|
||||||
|
// subsequent reads to expand the same trie instead of reloading from disk.
|
||||||
func (s *stateObject) getTrie() (Trie, error) {
|
func (s *stateObject) getTrie() (Trie, error) {
|
||||||
if s.trie == nil {
|
|
||||||
// Try fetching from prefetcher first
|
|
||||||
if s.data.Root != types.EmptyRootHash && s.db.prefetcher != nil {
|
|
||||||
// When the miner is creating the pending state, there is no prefetcher
|
|
||||||
s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root)
|
|
||||||
}
|
|
||||||
if s.trie == nil {
|
if s.trie == nil {
|
||||||
tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie)
|
tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -152,19 +140,41 @@ func (s *stateObject) getTrie() (Trie, error) {
|
||||||
}
|
}
|
||||||
s.trie = tr
|
s.trie = tr
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return s.trie, nil
|
return s.trie, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getPrefetchedTrie returns the associated trie, as populated by the prefetcher
|
||||||
|
// if it's available.
|
||||||
|
//
|
||||||
|
// Note, opposed to getTrie, this method will *NOT* blindly cache the resulting
|
||||||
|
// trie in the state object. The caller might want to do that, but it's cleaner
|
||||||
|
// to break the hidden interdependency between retrieving tries from the db or
|
||||||
|
// from the prefetcher.
|
||||||
|
func (s *stateObject) getPrefetchedTrie() (Trie, error) {
|
||||||
|
// If there's nothing to meaningfully return, let the user figure it out by
|
||||||
|
// pulling the trie from disk.
|
||||||
|
if s.data.Root == types.EmptyRootHash || s.db.prefetcher == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// Attempt to retrieve the trie from the pretecher
|
||||||
|
return s.db.prefetcher.trie(s.addrHash, s.data.Root)
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
// If we have a dirty value for this state entry, return it
|
value, _ := s.getState(key)
|
||||||
value, dirty := s.dirtyStorage[key]
|
|
||||||
if dirty {
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
// Otherwise return the entry's original value
|
|
||||||
return s.GetCommittedState(key)
|
// getState retrieves a value associated with the given storage key, along with
|
||||||
|
// its original value.
|
||||||
|
func (s *stateObject) getState(key common.Hash) (common.Hash, common.Hash) {
|
||||||
|
origin := s.GetCommittedState(key)
|
||||||
|
value, dirty := s.dirtyStorage[key]
|
||||||
|
if dirty {
|
||||||
|
return value, origin
|
||||||
|
}
|
||||||
|
return origin, origin
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommittedState retrieves a value from the committed account storage trie.
|
// GetCommittedState retrieves a value from the committed account storage trie.
|
||||||
|
|
@ -227,8 +237,9 @@ 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, origin := s.getState(key)
|
||||||
if prev == value {
|
if prev == value {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -236,34 +247,55 @@ func (s *stateObject) SetState(key, value common.Hash) {
|
||||||
s.db.journal.append(storageChange{
|
s.db.journal.append(storageChange{
|
||||||
account: &s.address,
|
account: &s.address,
|
||||||
key: key,
|
key: key,
|
||||||
prevalue: prev,
|
prevvalue: prev,
|
||||||
|
origvalue: origin,
|
||||||
})
|
})
|
||||||
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, origin)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stateObject) setState(key, value common.Hash) {
|
// setState updates a value in account dirty storage. The dirtiness will be
|
||||||
|
// removed if the value being set equals to the original value.
|
||||||
|
func (s *stateObject) setState(key common.Hash, value common.Hash, origin common.Hash) {
|
||||||
|
// Storage slot is set back to its original value, undo the dirty marker
|
||||||
|
if value == origin {
|
||||||
|
delete(s.dirtyStorage, key)
|
||||||
|
return
|
||||||
|
}
|
||||||
s.dirtyStorage[key] = 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
|
||||||
// committed later. It is invoked at the end of every transaction.
|
// committed later. It is invoked at the end of every transaction.
|
||||||
func (s *stateObject) finalise(prefetch bool) {
|
func (s *stateObject) finalise() {
|
||||||
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 structure.
|
||||||
|
delete(s.pendingStorage, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
|
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
|
||||||
s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch)
|
if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch); err != nil {
|
||||||
|
log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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
|
||||||
|
|
@ -272,30 +304,44 @@ func (s *stateObject) finalise(prefetch bool) {
|
||||||
// loading or updating of the trie, an error will be returned. Furthermore,
|
// loading or updating of the trie, an error will be returned. Furthermore,
|
||||||
// this function will return the mutated storage trie, or nil if there is no
|
// this function will return the mutated storage trie, or nil if there is no
|
||||||
// storage change at all.
|
// storage change at all.
|
||||||
|
//
|
||||||
|
// It assumes all the dirty storage slots have been finalized before.
|
||||||
func (s *stateObject) updateTrie() (Trie, error) {
|
func (s *stateObject) updateTrie() (Trie, error) {
|
||||||
// Make sure all dirty slots are finalized into the pending storage area
|
|
||||||
s.finalise(false)
|
|
||||||
|
|
||||||
// Short circuit if nothing changed, don't bother with hashing anything
|
// Short circuit if nothing changed, don't bother with hashing anything
|
||||||
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
|
// Retrieve a pretecher populated trie, or fall back to the database
|
||||||
defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now())
|
tr, err := s.getPrefetchedTrie()
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
// Fetcher retrieval failed, something's very wrong, abort
|
||||||
|
s.db.setError(err)
|
||||||
|
return nil, err
|
||||||
|
|
||||||
|
case tr == nil:
|
||||||
|
// Fetcher not running or empty trie, fallback to the database trie
|
||||||
|
tr, err = s.getTrie()
|
||||||
|
if err != nil {
|
||||||
|
s.db.setError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Prefetcher returned a live trie, swap it out for the current one
|
||||||
|
s.trie = tr
|
||||||
|
}
|
||||||
// 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
|
||||||
origin map[common.Hash][]byte
|
origin map[common.Hash][]byte
|
||||||
)
|
)
|
||||||
tr, err := s.getTrie()
|
|
||||||
if err != nil {
|
|
||||||
s.db.setError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Insert all the pending storage updates into the trie
|
// Insert all the pending storage updates into the trie
|
||||||
usedStorage := make([][]byte, 0, len(s.pendingStorage))
|
usedStorage := make([][]byte, 0, len(s.pendingStorage))
|
||||||
|
|
||||||
|
hasher := hasherPool.Get().(crypto.KeccakState)
|
||||||
|
defer hasherPool.Put(hasher)
|
||||||
|
|
||||||
// Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
|
// Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
|
||||||
// in circumstances similar to the following:
|
// in circumstances similar to the following:
|
||||||
//
|
//
|
||||||
|
|
@ -324,26 +370,30 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
||||||
s.db.setError(err)
|
s.db.setError(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s.db.StorageUpdated += 1
|
s.db.StorageUpdated.Add(1)
|
||||||
} else {
|
} else {
|
||||||
deletions = append(deletions, key)
|
deletions = append(deletions, key)
|
||||||
}
|
}
|
||||||
// Cache the mutated storage slots until commit
|
// Cache the mutated storage slots until commit
|
||||||
if storage == nil {
|
if storage == nil {
|
||||||
|
s.db.storagesLock.Lock()
|
||||||
if storage = s.db.storages[s.addrHash]; storage == nil {
|
if storage = s.db.storages[s.addrHash]; storage == nil {
|
||||||
storage = make(map[common.Hash][]byte)
|
storage = make(map[common.Hash][]byte)
|
||||||
s.db.storages[s.addrHash] = storage
|
s.db.storages[s.addrHash] = storage
|
||||||
}
|
}
|
||||||
|
s.db.storagesLock.Unlock()
|
||||||
}
|
}
|
||||||
khash := crypto.HashData(s.db.hasher, key[:])
|
khash := crypto.HashData(hasher, key[:])
|
||||||
storage[khash] = encoded // encoded will be nil if it's deleted
|
storage[khash] = encoded // encoded will be nil if it's deleted
|
||||||
|
|
||||||
// Cache the original value of mutated storage slots
|
// Cache the original value of mutated storage slots
|
||||||
if origin == nil {
|
if origin == nil {
|
||||||
|
s.db.storagesLock.Lock()
|
||||||
if origin = s.db.storagesOrigin[s.address]; origin == nil {
|
if origin = s.db.storagesOrigin[s.address]; origin == nil {
|
||||||
origin = make(map[common.Hash][]byte)
|
origin = make(map[common.Hash][]byte)
|
||||||
s.db.storagesOrigin[s.address] = origin
|
s.db.storagesOrigin[s.address] = origin
|
||||||
}
|
}
|
||||||
|
s.db.storagesLock.Unlock()
|
||||||
}
|
}
|
||||||
// Track the original value of slot only if it's mutated first time
|
// Track the original value of slot only if it's mutated first time
|
||||||
if _, ok := origin[khash]; !ok {
|
if _, ok := origin[khash]; !ok {
|
||||||
|
|
@ -363,7 +413,12 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
||||||
s.db.setError(err)
|
s.db.setError(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s.db.StorageDeleted += 1
|
s.db.StorageDeleted.Add(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 +436,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.
|
||||||
|
|
@ -458,17 +510,17 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
|
||||||
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 +535,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 +553,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()) {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -36,14 +38,39 @@ import (
|
||||||
"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"
|
||||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
"github.com/ethereum/go-ethereum/trie/triestate"
|
||||||
|
"github.com/ethereum/go-ethereum/trie/utils"
|
||||||
"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:
|
||||||
|
|
@ -71,16 +98,28 @@ type StateDB struct {
|
||||||
// These maps hold the state changes (including the corresponding
|
// These maps hold the state changes (including the corresponding
|
||||||
// original value) that occurred in this **block**.
|
// original value) that occurred in this **block**.
|
||||||
accounts map[common.Hash][]byte // The mutated accounts in 'slim RLP' encoding
|
accounts map[common.Hash][]byte // The mutated accounts in 'slim RLP' encoding
|
||||||
storages map[common.Hash]map[common.Hash][]byte // The mutated slots in prefix-zero trimmed rlp format
|
|
||||||
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
|
|
||||||
|
|
||||||
// This map holds 'live' objects, which will get modified while processing
|
storages map[common.Hash]map[common.Hash][]byte // The mutated slots in prefix-zero trimmed rlp format
|
||||||
// a state transition.
|
storagesOrigin map[common.Address]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format
|
||||||
|
storagesLock sync.Mutex // Mutex protecting the maps during concurrent updates/commits
|
||||||
|
|
||||||
|
// This map holds 'live' objects, which will get modified while
|
||||||
|
// 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 +160,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
|
||||||
|
|
@ -130,9 +168,9 @@ type StateDB struct {
|
||||||
TrieDBCommits time.Duration
|
TrieDBCommits time.Duration
|
||||||
|
|
||||||
AccountUpdated int
|
AccountUpdated int
|
||||||
StorageUpdated int
|
StorageUpdated atomic.Int64
|
||||||
AccountDeleted int
|
AccountDeleted int
|
||||||
StorageDeleted int
|
StorageDeleted atomic.Int64
|
||||||
|
|
||||||
// Testing hooks
|
// Testing hooks
|
||||||
onCommit func(states *triestate.Set) // Hook invoked when commit is performed
|
onCommit func(states *triestate.Set) // Hook invoked when commit is performed
|
||||||
|
|
@ -154,9 +192,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(),
|
||||||
|
|
@ -180,7 +217,8 @@ func (s *StateDB) SetLogger(l *tracing.Hooks) {
|
||||||
// commit phase, most of the needed data is already hot.
|
// commit phase, most of the needed data is already hot.
|
||||||
func (s *StateDB) StartPrefetcher(namespace string) {
|
func (s *StateDB) StartPrefetcher(namespace string) {
|
||||||
if s.prefetcher != nil {
|
if s.prefetcher != nil {
|
||||||
s.prefetcher.close()
|
s.prefetcher.terminate(false)
|
||||||
|
s.prefetcher.report()
|
||||||
s.prefetcher = nil
|
s.prefetcher = nil
|
||||||
}
|
}
|
||||||
if s.snap != nil {
|
if s.snap != nil {
|
||||||
|
|
@ -192,7 +230,8 @@ func (s *StateDB) StartPrefetcher(namespace string) {
|
||||||
// from the gathered metrics.
|
// from the gathered metrics.
|
||||||
func (s *StateDB) StopPrefetcher() {
|
func (s *StateDB) StopPrefetcher() {
|
||||||
if s.prefetcher != nil {
|
if s.prefetcher != nil {
|
||||||
s.prefetcher.close()
|
s.prefetcher.terminate(false)
|
||||||
|
s.prefetcher.report()
|
||||||
s.prefetcher = nil
|
s.prefetcher = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -472,8 +511,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -511,9 +549,6 @@ func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common
|
||||||
|
|
||||||
// updateStateObject writes the given object to the trie.
|
// updateStateObject writes the given object to the trie.
|
||||||
func (s *StateDB) updateStateObject(obj *stateObject) {
|
func (s *StateDB) updateStateObject(obj *stateObject) {
|
||||||
// Track the amount of time wasted on updating the account from the trie
|
|
||||||
defer func(start time.Time) { s.AccountUpdates += time.Since(start) }(time.Now())
|
|
||||||
|
|
||||||
// Encode the account and update the account trie
|
// Encode the account and update the account trie
|
||||||
addr := obj.Address()
|
addr := obj.Address()
|
||||||
if err := s.trie.UpdateAccount(addr, &obj.data); err != nil {
|
if err := s.trie.UpdateAccount(addr, &obj.data); err != nil {
|
||||||
|
|
@ -542,34 +577,22 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
|
||||||
|
|
||||||
// deleteStateObject removes the given object from the state trie.
|
// deleteStateObject removes the given object from the state trie.
|
||||||
func (s *StateDB) deleteStateObject(addr common.Address) {
|
func (s *StateDB) deleteStateObject(addr common.Address) {
|
||||||
// Track the amount of time wasted on deleting the account from the trie
|
|
||||||
defer func(start time.Time) { s.AccountUpdates += time.Since(start) }(time.Now())
|
|
||||||
|
|
||||||
// Delete the account from the trie
|
|
||||||
if err := s.trie.DeleteAccount(addr); err != nil {
|
if err := s.trie.DeleteAccount(addr); err != nil {
|
||||||
s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
|
s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 +645,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)
|
|
||||||
if prev == nil {
|
|
||||||
s.journal.append(createObjectChange{account: &addr})
|
s.journal.append(createObjectChange{account: &addr})
|
||||||
} else {
|
s.setStateObject(obj)
|
||||||
// The original account should be marked as destructed and all cached
|
return obj
|
||||||
// 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 +689,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 +716,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
|
|
||||||
}
|
}
|
||||||
|
// Deep copy the object state markers.
|
||||||
|
for addr, op := range s.mutations {
|
||||||
|
state.mutations[addr] = op.copy()
|
||||||
}
|
}
|
||||||
// Above, we don't copy the actual journal. This means that if the copy
|
|
||||||
// is copied, the loop above will be a no-op, since the copy's journal
|
|
||||||
// is empty. Thus, here we iterate over stateObjects, to enable copies
|
|
||||||
// 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 +733,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
|
||||||
|
|
@ -769,13 +741,6 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
// in the middle of a transaction.
|
// in the middle of a transaction.
|
||||||
state.accessList = s.accessList.Copy()
|
state.accessList = s.accessList.Copy()
|
||||||
state.transientStorage = s.transientStorage.Copy()
|
state.transientStorage = s.transientStorage.Copy()
|
||||||
|
|
||||||
// If there's a prefetcher running, make an inactive copy of it that can
|
|
||||||
// only access data but does not actively preload (since the user will not
|
|
||||||
// know that they need to explicitly terminate an active copy).
|
|
||||||
if s.prefetcher != nil {
|
|
||||||
state.prefetcher = s.prefetcher.copy()
|
|
||||||
}
|
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -825,7 +790,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 {
|
||||||
|
|
@ -845,19 +811,18 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||||
delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect)
|
delete(s.accountsOrigin, obj.address) // Clear out any previously updated account 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)
|
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()
|
||||||
|
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
|
||||||
addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure
|
addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure
|
||||||
}
|
}
|
||||||
if s.prefetcher != nil && len(addressesToPrefetch) > 0 {
|
if s.prefetcher != nil && len(addressesToPrefetch) > 0 {
|
||||||
s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch)
|
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch); err != nil {
|
||||||
|
log.Error("Failed to prefetch addresses", "addresses", len(addressesToPrefetch), "err", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Invalidate journal because reverting across transactions is not allowed.
|
// Invalidate journal because reverting across transactions is not allowed.
|
||||||
s.clearJournalAndRefund()
|
s.clearJournalAndRefund()
|
||||||
|
|
@ -870,39 +835,55 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
// Finalise all the dirty storage states and write them into the tries
|
// Finalise all the dirty storage states and write them into the tries
|
||||||
s.Finalise(deleteEmptyObjects)
|
s.Finalise(deleteEmptyObjects)
|
||||||
|
|
||||||
// If there was a trie prefetcher operating, it gets aborted and irrevocably
|
// If there was a trie prefetcher operating, terminate it async so that the
|
||||||
// modified after we start retrieving tries. Remove it from the statedb after
|
// individual storage tries can be updated as soon as the disk load finishes.
|
||||||
// this round of use.
|
|
||||||
//
|
|
||||||
// This is weird pre-byzantium since the first tx runs with a prefetcher and
|
|
||||||
// the remainder without, but pre-byzantium even the initial prefetcher is
|
|
||||||
// useless, so no sleep lost.
|
|
||||||
prefetcher := s.prefetcher
|
|
||||||
if s.prefetcher != nil {
|
if s.prefetcher != nil {
|
||||||
|
s.prefetcher.terminate(true)
|
||||||
defer func() {
|
defer func() {
|
||||||
s.prefetcher.close()
|
s.prefetcher.report()
|
||||||
s.prefetcher = nil
|
s.prefetcher = nil // Pre-byzantium, unset any used up prefetcher
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
// Although naively it makes sense to retrieve the account trie and then do
|
// Process all storage updates concurrently. The state object update root
|
||||||
// the contract storage and account updates sequentially, that short circuits
|
// method will internally call a blocking trie fetch from the prefetcher,
|
||||||
// the account prefetcher. Instead, let's process all the storage updates
|
// so there's no need to explicitly wait for the prefetchers to finish.
|
||||||
// first, giving the account prefetches just a few more milliseconds of time
|
var (
|
||||||
// to pull useful data from disk.
|
start = time.Now()
|
||||||
for addr := range s.stateObjectsPending {
|
workers errgroup.Group
|
||||||
if obj := s.stateObjects[addr]; !obj.deleted {
|
)
|
||||||
|
if s.db.TrieDB().IsVerkle() {
|
||||||
|
// Whilst MPT storage tries are independent, Verkle has one single trie
|
||||||
|
// for all the accounts and all the storage slots merged together. The
|
||||||
|
// former can thus be simply parallelized, but updating the latter will
|
||||||
|
// need concurrency support within the trie itself. That's a TODO for a
|
||||||
|
// later time.
|
||||||
|
workers.SetLimit(1)
|
||||||
|
}
|
||||||
|
for addr, op := range s.mutations {
|
||||||
|
if op.applied || op.isDelete() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
obj := s.stateObjects[addr] // closure for the task runner below
|
||||||
|
workers.Go(func() error {
|
||||||
obj.updateRoot()
|
obj.updateRoot()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
workers.Wait()
|
||||||
|
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.
|
||||||
if prefetcher != nil {
|
start = time.Now()
|
||||||
if trie := prefetcher.trie(common.Hash{}, s.originalRoot); trie != nil {
|
|
||||||
|
if s.prefetcher != nil {
|
||||||
|
if trie, err := s.prefetcher.trie(common.Hash{}, s.originalRoot); err != nil {
|
||||||
|
log.Error("Failed to retrieve account pre-fetcher trie", "err", err)
|
||||||
|
} else if trie != nil {
|
||||||
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 +894,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
|
||||||
}
|
}
|
||||||
|
|
@ -927,11 +916,10 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
s.deleteStateObject(deletedAddr)
|
s.deleteStateObject(deletedAddr)
|
||||||
s.AccountDeleted += 1
|
s.AccountDeleted += 1
|
||||||
}
|
}
|
||||||
if prefetcher != nil {
|
s.AccountUpdates += time.Since(start)
|
||||||
prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs)
|
|
||||||
}
|
if s.prefetcher != nil {
|
||||||
if len(s.stateObjectsPending) > 0 {
|
s.prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs)
|
||||||
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,72 +1157,119 @@ 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
|
||||||
}
|
}
|
||||||
|
// Run the storage updates concurrently to one another
|
||||||
|
workers.Go(func() error {
|
||||||
// Write any storage changes in the state object to its storage trie
|
// Write any storage changes in the state object to its storage trie
|
||||||
set, err := obj.commit()
|
set, err := obj.commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return err
|
||||||
}
|
}
|
||||||
// Merge the dirty nodes of storage trie into global set. It is possible
|
// Merge the dirty nodes of storage trie into global set. It is possible
|
||||||
// that the account was destructed and then resurrected in the same block.
|
// that the account was destructed and then resurrected in the same block.
|
||||||
// In this case, the node set is shared by both accounts.
|
// In this case, the node set is shared by both accounts.
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
|
||||||
if set != nil {
|
if set != nil {
|
||||||
if err := nodes.Merge(set); err != nil {
|
if err = nodes.Merge(set); err != nil {
|
||||||
return common.Hash{}, err
|
return err
|
||||||
}
|
}
|
||||||
updates, deleted := set.Size()
|
updates, deleted := set.Size()
|
||||||
storageTrieNodesUpdated += updates
|
storageTrieNodesUpdated += updates
|
||||||
storageTrieNodesDeleted += deleted
|
storageTrieNodesDeleted += deleted
|
||||||
}
|
}
|
||||||
|
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if codeWriter.ValueSize() > 0 {
|
// Schedule the code commits to run concurrently too. This shouldn't really
|
||||||
if err := codeWriter.Write(); err != nil {
|
// 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)
|
log.Crit("Failed to commit dirty codes", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Write the account trie changes, measuring the amount of wasted time
|
return nil
|
||||||
start := time.Now()
|
})
|
||||||
|
// Wait for everything to finish and update the metrics
|
||||||
root, set, err := s.trie.Commit(true)
|
if err := workers.Wait(); err != nil {
|
||||||
if 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(s.StorageUpdated.Load())
|
||||||
accountDeletedMeter.Mark(int64(s.AccountDeleted))
|
accountDeletedMeter.Mark(int64(s.AccountDeleted))
|
||||||
storageDeletedMeter.Mark(int64(s.StorageDeleted))
|
storageDeletedMeter.Mark(s.StorageDeleted.Load())
|
||||||
accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated))
|
accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated))
|
||||||
accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted))
|
accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted))
|
||||||
storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated))
|
storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated))
|
||||||
storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted))
|
storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted))
|
||||||
s.AccountUpdated, s.AccountDeleted = 0, 0
|
s.AccountUpdated, s.AccountDeleted = 0, 0
|
||||||
s.StorageUpdated, s.StorageDeleted = 0, 0
|
s.StorageUpdated.Store(0)
|
||||||
|
s.StorageDeleted.Store(0)
|
||||||
|
|
||||||
// If snapshotting is enabled, update the snapshot tree with this new version
|
// If snapshotting is enabled, update the snapshot tree with this new version
|
||||||
if s.snap != nil {
|
if s.snap != nil {
|
||||||
|
|
@ -1244,12 +1279,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 +1315,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
|
||||||
}
|
}
|
||||||
|
|
@ -1299,7 +1334,10 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
||||||
// - Add coinbase to access list (EIP-3651)
|
// - Add coinbase to access list (EIP-3651)
|
||||||
// - Reset transient storage (EIP-1153)
|
// - Reset transient storage (EIP-1153)
|
||||||
func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
|
func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
|
||||||
if rules.IsBerlin {
|
if rules.IsEIP2929 && rules.IsEIP4762 {
|
||||||
|
panic("eip2929 and eip4762 are both activated")
|
||||||
|
}
|
||||||
|
if rules.IsEIP2929 {
|
||||||
// Clear out any leftover from previous executions
|
// Clear out any leftover from previous executions
|
||||||
al := newAccessList()
|
al := newAccessList()
|
||||||
s.accessList = al
|
s.accessList = al
|
||||||
|
|
@ -1395,3 +1433,23 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StateDB) PointCache() *utils.PointCache {
|
||||||
|
return s.db.PointCache()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
if !s.Exist(addr) {
|
||||||
s.CreateAccount(addr)
|
s.CreateAccount(addr)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
if !s.Exist(addr) {
|
||||||
s.CreateAccount(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()
|
||||||
|
|
@ -1190,3 +1329,47 @@ func TestDeleteStorage(t *testing.T) {
|
||||||
t.Fatalf("difference found:\nfast: %v\nslow: %v\n", fastRes, slowRes)
|
t.Fatalf("difference found:\nfast: %v\nslow: %v\n", fastRes, slowRes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStorageDirtiness(t *testing.T) {
|
||||||
|
var (
|
||||||
|
disk = rawdb.NewMemoryDatabase()
|
||||||
|
tdb = triedb.NewDatabase(disk, nil)
|
||||||
|
db = NewDatabaseWithNodeDB(disk, tdb)
|
||||||
|
state, _ = New(types.EmptyRootHash, db, nil)
|
||||||
|
addr = common.HexToAddress("0x1")
|
||||||
|
checkDirty = func(key common.Hash, value common.Hash, dirty bool) {
|
||||||
|
obj := state.getStateObject(addr)
|
||||||
|
v, exist := obj.dirtyStorage[key]
|
||||||
|
if exist != dirty {
|
||||||
|
t.Fatalf("Unexpected dirty marker, want: %t, got: %t", dirty, exist)
|
||||||
|
}
|
||||||
|
if v != value {
|
||||||
|
t.Fatalf("Unexpected storage slot, want: %t, got: %t", value, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
state.CreateAccount(addr)
|
||||||
|
|
||||||
|
// the storage change is noop, no dirty marker
|
||||||
|
state.SetState(addr, common.Hash{0x1}, common.Hash{})
|
||||||
|
checkDirty(common.Hash{0x1}, common.Hash{}, false)
|
||||||
|
|
||||||
|
// the storage change is valid, dirty marker is expected
|
||||||
|
snap := state.Snapshot()
|
||||||
|
state.SetState(addr, common.Hash{0x1}, common.Hash{0x1})
|
||||||
|
checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
|
||||||
|
|
||||||
|
// the storage change is reverted, dirtiness should be revoked
|
||||||
|
state.RevertToSnapshot(snap)
|
||||||
|
checkDirty(common.Hash{0x1}, common.Hash{}, false)
|
||||||
|
|
||||||
|
// the storage is reset back to its original value, dirtiness should be revoked
|
||||||
|
state.SetState(addr, common.Hash{0x1}, common.Hash{0x1})
|
||||||
|
snap = state.Snapshot()
|
||||||
|
state.SetState(addr, common.Hash{0x1}, common.Hash{})
|
||||||
|
checkDirty(common.Hash{0x1}, common.Hash{}, false)
|
||||||
|
|
||||||
|
// the storage change is reverted, dirty value should be set back
|
||||||
|
state.RevertToSnapshot(snap)
|
||||||
|
checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,11 +34,20 @@ 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 value == (common.Hash{}) { // this is a 'delete'
|
||||||
|
if _, ok := t[addr]; ok {
|
||||||
|
delete(t[addr], key)
|
||||||
|
if len(t[addr]) == 0 {
|
||||||
|
delete(t, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if _, ok := t[addr]; !ok {
|
if _, ok := t[addr]; !ok {
|
||||||
t[addr] = make(Storage)
|
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`.
|
||||||
func (t transientStorage) Get(addr common.Address, key common.Hash) common.Hash {
|
func (t transientStorage) Get(addr common.Address, key common.Hash) common.Hash {
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -27,6 +28,10 @@ import (
|
||||||
var (
|
var (
|
||||||
// triePrefetchMetricsPrefix is the prefix under which to publish the metrics.
|
// triePrefetchMetricsPrefix is the prefix under which to publish the metrics.
|
||||||
triePrefetchMetricsPrefix = "trie/prefetch/"
|
triePrefetchMetricsPrefix = "trie/prefetch/"
|
||||||
|
|
||||||
|
// errTerminated is returned if a fetcher is attempted to be operated after it
|
||||||
|
// has already terminated.
|
||||||
|
errTerminated = errors.New("fetcher is already terminated")
|
||||||
)
|
)
|
||||||
|
|
||||||
// triePrefetcher is an active prefetcher, which receives accounts or storage
|
// triePrefetcher is an active prefetcher, which receives accounts or storage
|
||||||
|
|
@ -37,52 +42,64 @@ var (
|
||||||
type triePrefetcher struct {
|
type triePrefetcher struct {
|
||||||
db Database // Database to fetch trie nodes through
|
db Database // Database to fetch trie nodes through
|
||||||
root common.Hash // Root hash of the account trie for metrics
|
root common.Hash // Root hash of the account trie for metrics
|
||||||
fetches map[string]Trie // Partially or fully fetched tries. Only populated for inactive copies.
|
|
||||||
fetchers map[string]*subfetcher // Subfetchers for each trie
|
fetchers map[string]*subfetcher // Subfetchers for each trie
|
||||||
|
term chan struct{} // Channel to signal interruption
|
||||||
|
|
||||||
deliveryMissMeter metrics.Meter
|
deliveryMissMeter metrics.Meter
|
||||||
accountLoadMeter metrics.Meter
|
accountLoadMeter metrics.Meter
|
||||||
accountDupMeter metrics.Meter
|
accountDupMeter metrics.Meter
|
||||||
accountSkipMeter metrics.Meter
|
|
||||||
accountWasteMeter metrics.Meter
|
accountWasteMeter metrics.Meter
|
||||||
storageLoadMeter metrics.Meter
|
storageLoadMeter metrics.Meter
|
||||||
storageDupMeter metrics.Meter
|
storageDupMeter metrics.Meter
|
||||||
storageSkipMeter metrics.Meter
|
|
||||||
storageWasteMeter metrics.Meter
|
storageWasteMeter metrics.Meter
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher {
|
func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher {
|
||||||
prefix := triePrefetchMetricsPrefix + namespace
|
prefix := triePrefetchMetricsPrefix + namespace
|
||||||
p := &triePrefetcher{
|
return &triePrefetcher{
|
||||||
db: db,
|
db: db,
|
||||||
root: root,
|
root: root,
|
||||||
fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
|
fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
|
||||||
|
term: make(chan struct{}),
|
||||||
|
|
||||||
deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil),
|
deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil),
|
||||||
accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil),
|
accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil),
|
||||||
accountDupMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup", nil),
|
accountDupMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup", nil),
|
||||||
accountSkipMeter: metrics.GetOrRegisterMeter(prefix+"/account/skip", nil),
|
|
||||||
accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil),
|
accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil),
|
||||||
storageLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load", nil),
|
storageLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load", nil),
|
||||||
storageDupMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil),
|
storageDupMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil),
|
||||||
storageSkipMeter: metrics.GetOrRegisterMeter(prefix+"/storage/skip", nil),
|
|
||||||
storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil),
|
storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil),
|
||||||
}
|
}
|
||||||
return p
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// close iterates over all the subfetchers, aborts any that were left spinning
|
// terminate iterates over all the subfetchers and issues a terminateion request
|
||||||
// and reports the stats to the metrics subsystem.
|
// to all of them. Depending on the async parameter, the method will either block
|
||||||
func (p *triePrefetcher) close() {
|
// until all subfetchers spin down, or return immediately.
|
||||||
|
func (p *triePrefetcher) terminate(async bool) {
|
||||||
|
// Short circuit if the fetcher is already closed
|
||||||
|
select {
|
||||||
|
case <-p.term:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
// Termiante all sub-fetchers, sync or async, depending on the request
|
||||||
for _, fetcher := range p.fetchers {
|
for _, fetcher := range p.fetchers {
|
||||||
fetcher.abort() // safe to do multiple times
|
fetcher.terminate(async)
|
||||||
|
}
|
||||||
|
close(p.term)
|
||||||
|
}
|
||||||
|
|
||||||
|
// report aggregates the pre-fetching and usage metrics and reports them.
|
||||||
|
func (p *triePrefetcher) report() {
|
||||||
|
if !metrics.Enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, fetcher := range p.fetchers {
|
||||||
|
fetcher.wait() // ensure the fetcher's idle before poking in its internals
|
||||||
|
|
||||||
if metrics.Enabled {
|
|
||||||
if fetcher.root == p.root {
|
if fetcher.root == p.root {
|
||||||
p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
|
p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
|
||||||
p.accountDupMeter.Mark(int64(fetcher.dups))
|
p.accountDupMeter.Mark(int64(fetcher.dups))
|
||||||
p.accountSkipMeter.Mark(int64(len(fetcher.tasks)))
|
|
||||||
|
|
||||||
for _, key := range fetcher.used {
|
for _, key := range fetcher.used {
|
||||||
delete(fetcher.seen, string(key))
|
delete(fetcher.seen, string(key))
|
||||||
}
|
}
|
||||||
|
|
@ -90,8 +107,6 @@ func (p *triePrefetcher) close() {
|
||||||
} else {
|
} else {
|
||||||
p.storageLoadMeter.Mark(int64(len(fetcher.seen)))
|
p.storageLoadMeter.Mark(int64(len(fetcher.seen)))
|
||||||
p.storageDupMeter.Mark(int64(fetcher.dups))
|
p.storageDupMeter.Mark(int64(fetcher.dups))
|
||||||
p.storageSkipMeter.Mark(int64(len(fetcher.tasks)))
|
|
||||||
|
|
||||||
for _, key := range fetcher.used {
|
for _, key := range fetcher.used {
|
||||||
delete(fetcher.seen, string(key))
|
delete(fetcher.seen, string(key))
|
||||||
}
|
}
|
||||||
|
|
@ -99,98 +114,54 @@ func (p *triePrefetcher) close() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Clear out all fetchers (will crash on a second call, deliberate)
|
|
||||||
p.fetchers = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy creates a deep-but-inactive copy of the trie prefetcher. Any trie data
|
// prefetch schedules a batch of trie items to prefetch. After the prefetcher is
|
||||||
// already loaded will be copied over, but no goroutines will be started. This
|
// closed, all the following tasks scheduled will not be executed and an error
|
||||||
// is mostly used in the miner which creates a copy of it's actively mutated
|
// will be returned.
|
||||||
// state to be sealed while it may further mutate the state.
|
//
|
||||||
func (p *triePrefetcher) copy() *triePrefetcher {
|
// prefetch is called from two locations:
|
||||||
copy := &triePrefetcher{
|
//
|
||||||
db: p.db,
|
// 1. Finalize of the state-objects storage roots. This happens at the end
|
||||||
root: p.root,
|
// of every transaction, meaning that if several transactions touches
|
||||||
fetches: make(map[string]Trie), // Active prefetchers use the fetches map
|
// upon the same contract, the parameters invoking this method may be
|
||||||
|
// repeated.
|
||||||
deliveryMissMeter: p.deliveryMissMeter,
|
// 2. Finalize of the main account trie. This happens only once per block.
|
||||||
accountLoadMeter: p.accountLoadMeter,
|
func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr common.Address, keys [][]byte) error {
|
||||||
accountDupMeter: p.accountDupMeter,
|
// Ensure the subfetcher is still alive
|
||||||
accountSkipMeter: p.accountSkipMeter,
|
select {
|
||||||
accountWasteMeter: p.accountWasteMeter,
|
case <-p.term:
|
||||||
storageLoadMeter: p.storageLoadMeter,
|
return errTerminated
|
||||||
storageDupMeter: p.storageDupMeter,
|
default:
|
||||||
storageSkipMeter: p.storageSkipMeter,
|
|
||||||
storageWasteMeter: p.storageWasteMeter,
|
|
||||||
}
|
}
|
||||||
// If the prefetcher is already a copy, duplicate the data
|
|
||||||
if p.fetches != nil {
|
|
||||||
for root, fetch := range p.fetches {
|
|
||||||
if fetch == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
copy.fetches[root] = p.db.CopyTrie(fetch)
|
|
||||||
}
|
|
||||||
return copy
|
|
||||||
}
|
|
||||||
// Otherwise we're copying an active fetcher, retrieve the current states
|
|
||||||
for id, fetcher := range p.fetchers {
|
|
||||||
copy.fetches[id] = fetcher.peek()
|
|
||||||
}
|
|
||||||
return copy
|
|
||||||
}
|
|
||||||
|
|
||||||
// prefetch schedules a batch of trie items to prefetch.
|
|
||||||
func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr common.Address, keys [][]byte) {
|
|
||||||
// If the prefetcher is an inactive one, bail out
|
|
||||||
if p.fetches != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Active fetcher, schedule the retrievals
|
|
||||||
id := p.trieID(owner, root)
|
id := p.trieID(owner, root)
|
||||||
fetcher := p.fetchers[id]
|
fetcher := p.fetchers[id]
|
||||||
if fetcher == nil {
|
if fetcher == nil {
|
||||||
fetcher = newSubfetcher(p.db, p.root, owner, root, addr)
|
fetcher = newSubfetcher(p.db, p.root, owner, root, addr)
|
||||||
p.fetchers[id] = fetcher
|
p.fetchers[id] = fetcher
|
||||||
}
|
}
|
||||||
fetcher.schedule(keys)
|
return fetcher.schedule(keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
// trie returns the trie matching the root hash, or nil if the prefetcher doesn't
|
// trie returns the trie matching the root hash, blocking until the fetcher of
|
||||||
// have it.
|
// the given trie terminates. If no fetcher exists for the request, nil will be
|
||||||
func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie {
|
// returned.
|
||||||
// If the prefetcher is inactive, return from existing deep copies
|
func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) (Trie, error) {
|
||||||
id := p.trieID(owner, root)
|
// Bail if no trie was prefetched for this root
|
||||||
if p.fetches != nil {
|
fetcher := p.fetchers[p.trieID(owner, root)]
|
||||||
trie := p.fetches[id]
|
|
||||||
if trie == nil {
|
|
||||||
p.deliveryMissMeter.Mark(1)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return p.db.CopyTrie(trie)
|
|
||||||
}
|
|
||||||
// Otherwise the prefetcher is active, bail if no trie was prefetched for this root
|
|
||||||
fetcher := p.fetchers[id]
|
|
||||||
if fetcher == nil {
|
if fetcher == nil {
|
||||||
|
log.Error("Prefetcher missed to load trie", "owner", owner, "root", root)
|
||||||
p.deliveryMissMeter.Mark(1)
|
p.deliveryMissMeter.Mark(1)
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
// Interrupt the prefetcher if it's by any chance still running and return
|
// Subfetcher exists, retrieve its trie
|
||||||
// a copy of any pre-loaded trie.
|
return fetcher.peek(), nil
|
||||||
fetcher.abort() // safe to do multiple times
|
|
||||||
|
|
||||||
trie := fetcher.peek()
|
|
||||||
if trie == nil {
|
|
||||||
p.deliveryMissMeter.Mark(1)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return trie
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// used marks a batch of state items used to allow creating statistics as to
|
// used marks a batch of state items used to allow creating statistics as to
|
||||||
// how useful or wasteful the prefetcher is.
|
// how useful or wasteful the fetcher is.
|
||||||
func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte) {
|
func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte) {
|
||||||
if fetcher := p.fetchers[p.trieID(owner, root)]; fetcher != nil {
|
if fetcher := p.fetchers[p.trieID(owner, root)]; fetcher != nil {
|
||||||
|
fetcher.wait() // ensure the fetcher's idle before poking in its internals
|
||||||
fetcher.used = used
|
fetcher.used = used
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -221,7 +192,6 @@ type subfetcher struct {
|
||||||
wake chan struct{} // Wake channel if a new task is scheduled
|
wake chan struct{} // Wake channel if a new task is scheduled
|
||||||
stop chan struct{} // Channel to interrupt processing
|
stop chan struct{} // Channel to interrupt processing
|
||||||
term chan struct{} // Channel to signal interruption
|
term chan struct{} // Channel to signal interruption
|
||||||
copy chan chan Trie // Channel to request a copy of the current trie
|
|
||||||
|
|
||||||
seen map[string]struct{} // Tracks the entries already loaded
|
seen map[string]struct{} // Tracks the entries already loaded
|
||||||
dups int // Number of duplicate preload tasks
|
dups int // Number of duplicate preload tasks
|
||||||
|
|
@ -240,7 +210,6 @@ func newSubfetcher(db Database, state common.Hash, owner common.Hash, root commo
|
||||||
wake: make(chan struct{}, 1),
|
wake: make(chan struct{}, 1),
|
||||||
stop: make(chan struct{}),
|
stop: make(chan struct{}),
|
||||||
term: make(chan struct{}),
|
term: make(chan struct{}),
|
||||||
copy: make(chan chan Trie),
|
|
||||||
seen: make(map[string]struct{}),
|
seen: make(map[string]struct{}),
|
||||||
}
|
}
|
||||||
go sf.loop()
|
go sf.loop()
|
||||||
|
|
@ -248,50 +217,61 @@ func newSubfetcher(db Database, state common.Hash, owner common.Hash, root commo
|
||||||
}
|
}
|
||||||
|
|
||||||
// schedule adds a batch of trie keys to the queue to prefetch.
|
// schedule adds a batch of trie keys to the queue to prefetch.
|
||||||
func (sf *subfetcher) schedule(keys [][]byte) {
|
func (sf *subfetcher) schedule(keys [][]byte) error {
|
||||||
|
// Ensure the subfetcher is still alive
|
||||||
|
select {
|
||||||
|
case <-sf.term:
|
||||||
|
return errTerminated
|
||||||
|
default:
|
||||||
|
}
|
||||||
// Append the tasks to the current queue
|
// Append the tasks to the current queue
|
||||||
sf.lock.Lock()
|
sf.lock.Lock()
|
||||||
sf.tasks = append(sf.tasks, keys...)
|
sf.tasks = append(sf.tasks, keys...)
|
||||||
sf.lock.Unlock()
|
sf.lock.Unlock()
|
||||||
|
|
||||||
// Notify the prefetcher, it's fine if it's already terminated
|
// Notify the background thread to execute scheduled tasks
|
||||||
select {
|
select {
|
||||||
case sf.wake <- struct{}{}:
|
case sf.wake <- struct{}{}:
|
||||||
|
// Wake signal sent
|
||||||
default:
|
default:
|
||||||
|
// Wake signal not sent as a previous is already queued
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// peek tries to retrieve a deep copy of the fetcher's trie in whatever form it
|
|
||||||
// is currently.
|
|
||||||
func (sf *subfetcher) peek() Trie {
|
|
||||||
ch := make(chan Trie)
|
|
||||||
select {
|
|
||||||
case sf.copy <- ch:
|
|
||||||
// Subfetcher still alive, return copy from it
|
|
||||||
return <-ch
|
|
||||||
|
|
||||||
case <-sf.term:
|
|
||||||
// Subfetcher already terminated, return a copy directly
|
|
||||||
if sf.trie == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return sf.db.CopyTrie(sf.trie)
|
|
||||||
}
|
// wait blocks until the subfetcher terminates. This method is used to block on
|
||||||
|
// an async termination before accessing internal fields from the fetcher.
|
||||||
|
func (sf *subfetcher) wait() {
|
||||||
|
<-sf.term
|
||||||
}
|
}
|
||||||
|
|
||||||
// abort interrupts the subfetcher immediately. It is safe to call abort multiple
|
// peek retrieves the fetcher's trie, populated with any pre-fetched data. The
|
||||||
// times but it is not thread safe.
|
// returned trie will be a shallow copy, so modifying it will break subsequent
|
||||||
func (sf *subfetcher) abort() {
|
// peeks for the original data. The method will block until all the scheduled
|
||||||
|
// data has been loaded and the fethcer terminated.
|
||||||
|
func (sf *subfetcher) peek() Trie {
|
||||||
|
// Block until the fertcher terminates, then retrieve the trie
|
||||||
|
sf.wait()
|
||||||
|
return sf.trie
|
||||||
|
}
|
||||||
|
|
||||||
|
// terminate requests the subfetcher to stop accepting new tasks and spin down
|
||||||
|
// as soon as everything is loaded. Depending on the async parameter, the method
|
||||||
|
// will either block until all disk loads finish or return immediately.
|
||||||
|
func (sf *subfetcher) terminate(async bool) {
|
||||||
select {
|
select {
|
||||||
case <-sf.stop:
|
case <-sf.stop:
|
||||||
default:
|
default:
|
||||||
close(sf.stop)
|
close(sf.stop)
|
||||||
}
|
}
|
||||||
|
if async {
|
||||||
|
return
|
||||||
|
}
|
||||||
<-sf.term
|
<-sf.term
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop waits for new tasks to be scheduled and keeps loading them until it runs
|
// loop loads newly-scheduled trie tasks as they are received and loads them, stopping
|
||||||
// out of tasks or its underlying trie is retrieved for committing.
|
// when requested.
|
||||||
func (sf *subfetcher) loop() {
|
func (sf *subfetcher) loop() {
|
||||||
// No matter how the loop stops, signal anyone waiting that it's terminated
|
// No matter how the loop stops, signal anyone waiting that it's terminated
|
||||||
defer close(sf.term)
|
defer close(sf.term)
|
||||||
|
|
@ -305,8 +285,6 @@ func (sf *subfetcher) loop() {
|
||||||
}
|
}
|
||||||
sf.trie = trie
|
sf.trie = trie
|
||||||
} else {
|
} else {
|
||||||
// The trie argument can be nil as verkle doesn't support prefetching
|
|
||||||
// yet. TODO FIX IT(rjl493456442), otherwise code will panic here.
|
|
||||||
trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil)
|
trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
|
log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
|
||||||
|
|
@ -318,31 +296,17 @@ func (sf *subfetcher) loop() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-sf.wake:
|
case <-sf.wake:
|
||||||
// Subfetcher was woken up, retrieve any tasks to avoid spinning the lock
|
// Execute all remaining tasks in single run
|
||||||
sf.lock.Lock()
|
sf.lock.Lock()
|
||||||
tasks := sf.tasks
|
tasks := sf.tasks
|
||||||
sf.tasks = nil
|
sf.tasks = nil
|
||||||
sf.lock.Unlock()
|
sf.lock.Unlock()
|
||||||
|
|
||||||
// Prefetch any tasks until the loop is interrupted
|
for _, task := range tasks {
|
||||||
for i, task := range tasks {
|
|
||||||
select {
|
|
||||||
case <-sf.stop:
|
|
||||||
// If termination is requested, add any leftover back and return
|
|
||||||
sf.lock.Lock()
|
|
||||||
sf.tasks = append(sf.tasks, tasks[i:]...)
|
|
||||||
sf.lock.Unlock()
|
|
||||||
return
|
|
||||||
|
|
||||||
case ch := <-sf.copy:
|
|
||||||
// Somebody wants a copy of the current trie, grant them
|
|
||||||
ch <- sf.db.CopyTrie(sf.trie)
|
|
||||||
|
|
||||||
default:
|
|
||||||
// No termination request yet, prefetch the next entry
|
|
||||||
if _, ok := sf.seen[string(task)]; ok {
|
if _, ok := sf.seen[string(task)]; ok {
|
||||||
sf.dups++
|
sf.dups++
|
||||||
} else {
|
continue
|
||||||
|
}
|
||||||
if len(task) == common.AddressLength {
|
if len(task) == common.AddressLength {
|
||||||
sf.trie.GetAccount(common.BytesToAddress(task))
|
sf.trie.GetAccount(common.BytesToAddress(task))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -350,16 +314,20 @@ func (sf *subfetcher) loop() {
|
||||||
}
|
}
|
||||||
sf.seen[string(task)] = struct{}{}
|
sf.seen[string(task)] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case ch := <-sf.copy:
|
|
||||||
// Somebody wants a copy of the current trie, grant them
|
|
||||||
ch <- sf.db.CopyTrie(sf.trie)
|
|
||||||
|
|
||||||
case <-sf.stop:
|
case <-sf.stop:
|
||||||
// Termination is requested, abort and leave remaining tasks
|
// Termination is requested, abort if no more tasks are pending. If
|
||||||
|
// there are some, exhaust them first.
|
||||||
|
sf.lock.Lock()
|
||||||
|
done := sf.tasks == nil
|
||||||
|
sf.lock.Unlock()
|
||||||
|
|
||||||
|
if done {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Some tasks are pending, loop and pick them up (that wake branch
|
||||||
|
// will be selected eventually, whilst stop remains closed to this
|
||||||
|
// branch will also run afterwards).
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package state
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
|
@ -46,68 +45,20 @@ func filledStateDB() *StateDB {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCopyAndClose(t *testing.T) {
|
func TestUseAfterTerminate(t *testing.T) {
|
||||||
db := filledStateDB()
|
db := filledStateDB()
|
||||||
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
|
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
|
||||||
skey := common.HexToHash("aaa")
|
skey := common.HexToHash("aaa")
|
||||||
prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
|
|
||||||
prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
a := prefetcher.trie(common.Hash{}, db.originalRoot)
|
|
||||||
prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
|
|
||||||
b := prefetcher.trie(common.Hash{}, db.originalRoot)
|
|
||||||
cpy := prefetcher.copy()
|
|
||||||
cpy.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
|
|
||||||
cpy.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
|
|
||||||
c := cpy.trie(common.Hash{}, db.originalRoot)
|
|
||||||
prefetcher.close()
|
|
||||||
cpy2 := cpy.copy()
|
|
||||||
cpy2.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
|
|
||||||
d := cpy2.trie(common.Hash{}, db.originalRoot)
|
|
||||||
cpy.close()
|
|
||||||
cpy2.close()
|
|
||||||
if a.Hash() != b.Hash() || a.Hash() != c.Hash() || a.Hash() != d.Hash() {
|
|
||||||
t.Fatalf("Invalid trie, hashes should be equal: %v %v %v %v", a.Hash(), b.Hash(), c.Hash(), d.Hash())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUseAfterClose(t *testing.T) {
|
if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}); err != nil {
|
||||||
db := filledStateDB()
|
t.Errorf("Prefetch failed before terminate: %v", err)
|
||||||
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
|
|
||||||
skey := common.HexToHash("aaa")
|
|
||||||
prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
|
|
||||||
a := prefetcher.trie(common.Hash{}, db.originalRoot)
|
|
||||||
prefetcher.close()
|
|
||||||
b := prefetcher.trie(common.Hash{}, db.originalRoot)
|
|
||||||
if a == nil {
|
|
||||||
t.Fatal("Prefetching before close should not return nil")
|
|
||||||
}
|
|
||||||
if b != nil {
|
|
||||||
t.Fatal("Trie after close should return nil")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
prefetcher.terminate(false)
|
||||||
|
|
||||||
func TestCopyClose(t *testing.T) {
|
if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}); err == nil {
|
||||||
db := filledStateDB()
|
t.Errorf("Prefetch succeeded after terminate: %v", err)
|
||||||
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
|
|
||||||
skey := common.HexToHash("aaa")
|
|
||||||
prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
|
|
||||||
cpy := prefetcher.copy()
|
|
||||||
a := prefetcher.trie(common.Hash{}, db.originalRoot)
|
|
||||||
b := cpy.trie(common.Hash{}, db.originalRoot)
|
|
||||||
prefetcher.close()
|
|
||||||
c := prefetcher.trie(common.Hash{}, db.originalRoot)
|
|
||||||
d := cpy.trie(common.Hash{}, db.originalRoot)
|
|
||||||
if a == nil {
|
|
||||||
t.Fatal("Prefetching before close should not return nil")
|
|
||||||
}
|
}
|
||||||
if b == nil {
|
if _, err := prefetcher.trie(common.Hash{}, db.originalRoot); err != nil {
|
||||||
t.Fatal("Copy trie should return nil")
|
t.Errorf("Trie retrieval failed after terminate: %v", err)
|
||||||
}
|
|
||||||
if c != nil {
|
|
||||||
t.Fatal("Trie after close should return nil")
|
|
||||||
}
|
|
||||||
if d == nil {
|
|
||||||
t.Fatal("Copy trie should not return nil")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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{
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
@ -481,7 +482,7 @@ func TestProcessVerkle(t *testing.T) {
|
||||||
txCost1 := params.TxGas
|
txCost1 := params.TxGas
|
||||||
txCost2 := params.TxGas
|
txCost2 := params.TxGas
|
||||||
contractCreationCost := intrinsicContractCreationGas + uint64(2039 /* execution costs */)
|
contractCreationCost := intrinsicContractCreationGas + uint64(2039 /* execution costs */)
|
||||||
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(293644 /* execution costs */)
|
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(57444 /* execution costs */)
|
||||||
blockGasUsagesExpected := []uint64{
|
blockGasUsagesExpected := []uint64{
|
||||||
txCost1*2 + txCost2,
|
txCost1*2 + txCost2,
|
||||||
txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas,
|
txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas,
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ func (result *ExecutionResult) Revert() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
|
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
|
||||||
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
|
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
|
||||||
// Set the starting gas for the raw transaction
|
// Set the starting gas for the raw transaction
|
||||||
var gas uint64
|
var gas uint64
|
||||||
if isContractCreation && isHomestead {
|
if isContractCreation && isHomestead {
|
||||||
|
|
@ -240,8 +240,9 @@ func (st *StateTransition) buyGas() error {
|
||||||
if st.msg.GasFeeCap != nil {
|
if st.msg.GasFeeCap != nil {
|
||||||
balanceCheck.SetUint64(st.msg.GasLimit)
|
balanceCheck.SetUint64(st.msg.GasLimit)
|
||||||
balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
|
balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
|
||||||
balanceCheck.Add(balanceCheck, st.msg.Value)
|
|
||||||
}
|
}
|
||||||
|
balanceCheck.Add(balanceCheck, st.msg.Value)
|
||||||
|
|
||||||
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
|
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
|
||||||
if blobGas := st.blobGasUsed(); blobGas > 0 {
|
if blobGas := st.blobGasUsed(); blobGas > 0 {
|
||||||
// Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap
|
// Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap
|
||||||
|
|
@ -405,6 +406,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
||||||
}
|
}
|
||||||
st.gasRemaining -= gas
|
st.gasRemaining -= gas
|
||||||
|
|
||||||
|
if rules.IsEIP4762 {
|
||||||
|
st.evm.AccessEvents.AddTxOrigin(msg.From)
|
||||||
|
|
||||||
|
if targetAddr := msg.To; targetAddr != nil {
|
||||||
|
st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check clause 6
|
// Check clause 6
|
||||||
value, overflow := uint256.FromBig(msg.Value)
|
value, overflow := uint256.FromBig(msg.Value)
|
||||||
if overflow {
|
if overflow {
|
||||||
|
|
@ -458,6 +467,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
||||||
fee := new(uint256.Int).SetUint64(st.gasUsed())
|
fee := new(uint256.Int).SetUint64(st.gasUsed())
|
||||||
fee.Mul(fee, effectiveTipU256)
|
fee.Mul(fee, effectiveTipU256)
|
||||||
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
|
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
|
||||||
|
|
||||||
|
// add the coinbase to the witness iff the fee is greater than 0
|
||||||
|
if rules.IsEIP4762 && fee.Sign() != 0 {
|
||||||
|
st.evm.AccessEvents.BalanceGas(st.evm.Context.Coinbase, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ExecutionResult{
|
return &ExecutionResult{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -107,6 +115,9 @@ type (
|
||||||
// BlockchainInitHook is called when the blockchain is initialized.
|
// BlockchainInitHook is called when the blockchain is initialized.
|
||||||
BlockchainInitHook = func(chainConfig *params.ChainConfig)
|
BlockchainInitHook = func(chainConfig *params.ChainConfig)
|
||||||
|
|
||||||
|
// CloseHook is called when the blockchain closes.
|
||||||
|
CloseHook = func()
|
||||||
|
|
||||||
// BlockStartHook is called before executing `block`.
|
// BlockStartHook is called before executing `block`.
|
||||||
// `td` is the total difficulty prior to `block`.
|
// `td` is the total difficulty prior to `block`.
|
||||||
BlockStartHook = func(event BlockEvent)
|
BlockStartHook = func(event BlockEvent)
|
||||||
|
|
@ -122,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 -
|
||||||
*/
|
*/
|
||||||
|
|
@ -153,10 +180,13 @@ type Hooks struct {
|
||||||
OnGasChange GasChangeHook
|
OnGasChange GasChangeHook
|
||||||
// Chain events
|
// Chain events
|
||||||
OnBlockchainInit BlockchainInitHook
|
OnBlockchainInit BlockchainInitHook
|
||||||
|
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
|
||||||
|
|
@ -268,6 +298,12 @@ const (
|
||||||
GasChangeCallStorageColdAccess GasChangeReason = 13
|
GasChangeCallStorageColdAccess GasChangeReason = 13
|
||||||
// GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert.
|
// GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert.
|
||||||
GasChangeCallFailedExecution GasChangeReason = 14
|
GasChangeCallFailedExecution GasChangeReason = 14
|
||||||
|
// GasChangeWitnessContractInit is the amount charged for adding to the witness during the contract creation initialization step
|
||||||
|
GasChangeWitnessContractInit GasChangeReason = 15
|
||||||
|
// GasChangeWitnessContractCreation is the amount charged for adding to the witness during the contract creation finalization step
|
||||||
|
GasChangeWitnessContractCreation GasChangeReason = 16
|
||||||
|
// GasChangeWitnessCodeChunk is the amount charged for touching one or more contract code chunks
|
||||||
|
GasChangeWitnessCodeChunk GasChangeReason = 17
|
||||||
|
|
||||||
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
|
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
|
||||||
// it will be "manually" tracked by a direct emit of the gas change event.
|
// it will be "manually" tracked by a direct emit of the gas change event.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,18 @@ func getData(data []byte, start uint64, size uint64) []byte {
|
||||||
return common.RightPadBytes(data[start:end], int(size))
|
return common.RightPadBytes(data[start:end], int(size))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDataAndAdjustedBounds(data []byte, start uint64, size uint64) (codeCopyPadded []byte, actualStart uint64, sizeNonPadded uint64) {
|
||||||
|
length := uint64(len(data))
|
||||||
|
if start > length {
|
||||||
|
start = length
|
||||||
|
}
|
||||||
|
end := start + size
|
||||||
|
if end > length {
|
||||||
|
end = length
|
||||||
|
}
|
||||||
|
return common.RightPadBytes(data[start:end], int(size)), start, end - start
|
||||||
|
}
|
||||||
|
|
||||||
// toWordSize returns the ceiled word size required for memory expansion.
|
// toWordSize returns the ceiled word size required for memory expansion.
|
||||||
func toWordSize(size uint64) uint64 {
|
func toWordSize(size uint64) uint64 {
|
||||||
if size > math.MaxUint64-31 {
|
if size > math.MaxUint64-31 {
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,9 @@ type Contract struct {
|
||||||
CodeAddr *common.Address
|
CodeAddr *common.Address
|
||||||
Input []byte
|
Input []byte
|
||||||
|
|
||||||
|
// is the execution frame represented by this object a contract deployment
|
||||||
|
IsDeployment bool
|
||||||
|
|
||||||
Gas uint64
|
Gas uint64
|
||||||
value *uint256.Int
|
value *uint256.Int
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,8 @@ var PrecompiledContractsPrague = map[common.Address]PrecompiledContract{
|
||||||
|
|
||||||
var PrecompiledContractsBLS = PrecompiledContractsPrague
|
var PrecompiledContractsBLS = PrecompiledContractsPrague
|
||||||
|
|
||||||
|
var PrecompiledContractsVerkle = PrecompiledContractsPrague
|
||||||
|
|
||||||
var (
|
var (
|
||||||
PrecompiledAddressesPrague []common.Address
|
PrecompiledAddressesPrague []common.Address
|
||||||
PrecompiledAddressesCancun []common.Address
|
PrecompiledAddressesCancun []common.Address
|
||||||
|
|
@ -705,6 +707,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 +738,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 +796,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 +841,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 +873,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 +931,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 +1125,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 +1158,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
|
||||||
|
|
|
||||||
214
core/vm/eips.go
214
core/vm/eips.go
|
|
@ -18,9 +18,11 @@ package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
@ -37,6 +39,7 @@ var activators = map[int]func(*JumpTable){
|
||||||
1884: enable1884,
|
1884: enable1884,
|
||||||
1344: enable1344,
|
1344: enable1344,
|
||||||
1153: enable1153,
|
1153: enable1153,
|
||||||
|
4762: enable4762,
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableEIP enables the given EIP on the config.
|
// EnableEIP enables the given EIP on the config.
|
||||||
|
|
@ -319,3 +322,214 @@ func enable6780(jt *JumpTable) {
|
||||||
maxStack: maxStack(1, 0),
|
maxStack: maxStack(1, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
stack = scope.Stack
|
||||||
|
a = stack.pop()
|
||||||
|
memOffset = stack.pop()
|
||||||
|
codeOffset = stack.pop()
|
||||||
|
length = stack.pop()
|
||||||
|
)
|
||||||
|
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
||||||
|
if overflow {
|
||||||
|
uint64CodeOffset = math.MaxUint64
|
||||||
|
}
|
||||||
|
addr := common.Address(a.Bytes20())
|
||||||
|
code := interpreter.evm.StateDB.GetCode(addr)
|
||||||
|
contract := &Contract{
|
||||||
|
Code: code,
|
||||||
|
self: AccountRef(addr),
|
||||||
|
}
|
||||||
|
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
|
||||||
|
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false)
|
||||||
|
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) {
|
||||||
|
scope.Contract.Gas = 0
|
||||||
|
return nil, ErrOutOfGas
|
||||||
|
}
|
||||||
|
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy)
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// opPush1EIP4762 handles the special case of PUSH1 opcode for EIP-4762, which
|
||||||
|
// need not worry about the adjusted bound logic when adding the PUSHDATA to
|
||||||
|
// the list of access events.
|
||||||
|
func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
codeLen = uint64(len(scope.Contract.Code))
|
||||||
|
integer = new(uint256.Int)
|
||||||
|
)
|
||||||
|
*pc += 1
|
||||||
|
if *pc < codeLen {
|
||||||
|
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
|
||||||
|
|
||||||
|
if !scope.Contract.IsDeployment && *pc%31 == 0 {
|
||||||
|
// touch next chunk if PUSH1 is at the boundary. if so, *pc has
|
||||||
|
// advanced past this boundary.
|
||||||
|
contractAddr := scope.Contract.Address()
|
||||||
|
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false)
|
||||||
|
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) {
|
||||||
|
scope.Contract.Gas = 0
|
||||||
|
return nil, ErrOutOfGas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scope.Stack.push(integer.Clear())
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePushEIP4762(size uint64, pushByteSize int) executionFunc {
|
||||||
|
return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
codeLen = len(scope.Contract.Code)
|
||||||
|
start = min(codeLen, int(*pc+1))
|
||||||
|
end = min(codeLen, start+pushByteSize)
|
||||||
|
)
|
||||||
|
scope.Stack.push(new(uint256.Int).SetBytes(
|
||||||
|
common.RightPadBytes(
|
||||||
|
scope.Contract.Code[start:end],
|
||||||
|
pushByteSize,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if !scope.Contract.IsDeployment {
|
||||||
|
contractAddr := scope.Contract.Address()
|
||||||
|
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false)
|
||||||
|
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) {
|
||||||
|
scope.Contract.Gas = 0
|
||||||
|
return nil, ErrOutOfGas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*pc += size
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func enable4762(jt *JumpTable) {
|
||||||
|
jt[SSTORE] = &operation{
|
||||||
|
dynamicGas: gasSStore4762,
|
||||||
|
execute: opSstore,
|
||||||
|
minStack: minStack(2, 0),
|
||||||
|
maxStack: maxStack(2, 0),
|
||||||
|
}
|
||||||
|
jt[SLOAD] = &operation{
|
||||||
|
dynamicGas: gasSLoad4762,
|
||||||
|
execute: opSload,
|
||||||
|
minStack: minStack(1, 1),
|
||||||
|
maxStack: maxStack(1, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
jt[BALANCE] = &operation{
|
||||||
|
execute: opBalance,
|
||||||
|
dynamicGas: gasBalance4762,
|
||||||
|
minStack: minStack(1, 1),
|
||||||
|
maxStack: maxStack(1, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
jt[EXTCODESIZE] = &operation{
|
||||||
|
execute: opExtCodeSize,
|
||||||
|
dynamicGas: gasExtCodeSize4762,
|
||||||
|
minStack: minStack(1, 1),
|
||||||
|
maxStack: maxStack(1, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
jt[EXTCODEHASH] = &operation{
|
||||||
|
execute: opExtCodeHash,
|
||||||
|
dynamicGas: gasExtCodeHash4762,
|
||||||
|
minStack: minStack(1, 1),
|
||||||
|
maxStack: maxStack(1, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
jt[EXTCODECOPY] = &operation{
|
||||||
|
execute: opExtCodeCopyEIP4762,
|
||||||
|
dynamicGas: gasExtCodeCopyEIP4762,
|
||||||
|
minStack: minStack(4, 0),
|
||||||
|
maxStack: maxStack(4, 0),
|
||||||
|
memorySize: memoryExtCodeCopy,
|
||||||
|
}
|
||||||
|
|
||||||
|
jt[CODECOPY] = &operation{
|
||||||
|
execute: opCodeCopy,
|
||||||
|
constantGas: GasFastestStep,
|
||||||
|
dynamicGas: gasCodeCopyEip4762,
|
||||||
|
minStack: minStack(3, 0),
|
||||||
|
maxStack: maxStack(3, 0),
|
||||||
|
memorySize: memoryCodeCopy,
|
||||||
|
}
|
||||||
|
|
||||||
|
jt[SELFDESTRUCT] = &operation{
|
||||||
|
execute: opSelfdestruct6780,
|
||||||
|
dynamicGas: gasSelfdestructEIP4762,
|
||||||
|
constantGas: params.SelfdestructGasEIP150,
|
||||||
|
minStack: minStack(1, 0),
|
||||||
|
maxStack: maxStack(1, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
jt[CREATE] = &operation{
|
||||||
|
execute: opCreate,
|
||||||
|
constantGas: params.CreateNGasEip4762,
|
||||||
|
dynamicGas: gasCreateEip3860,
|
||||||
|
minStack: minStack(3, 1),
|
||||||
|
maxStack: maxStack(3, 1),
|
||||||
|
memorySize: memoryCreate,
|
||||||
|
}
|
||||||
|
|
||||||
|
jt[CREATE2] = &operation{
|
||||||
|
execute: opCreate2,
|
||||||
|
constantGas: params.CreateNGasEip4762,
|
||||||
|
dynamicGas: gasCreate2Eip3860,
|
||||||
|
minStack: minStack(4, 1),
|
||||||
|
maxStack: maxStack(4, 1),
|
||||||
|
memorySize: memoryCreate2,
|
||||||
|
}
|
||||||
|
|
||||||
|
jt[CALL] = &operation{
|
||||||
|
execute: opCall,
|
||||||
|
dynamicGas: gasCallEIP4762,
|
||||||
|
minStack: minStack(7, 1),
|
||||||
|
maxStack: maxStack(7, 1),
|
||||||
|
memorySize: memoryCall,
|
||||||
|
}
|
||||||
|
|
||||||
|
jt[CALLCODE] = &operation{
|
||||||
|
execute: opCallCode,
|
||||||
|
dynamicGas: gasCallCodeEIP4762,
|
||||||
|
minStack: minStack(7, 1),
|
||||||
|
maxStack: maxStack(7, 1),
|
||||||
|
memorySize: memoryCall,
|
||||||
|
}
|
||||||
|
|
||||||
|
jt[STATICCALL] = &operation{
|
||||||
|
execute: opStaticCall,
|
||||||
|
dynamicGas: gasStaticCallEIP4762,
|
||||||
|
minStack: minStack(6, 1),
|
||||||
|
maxStack: maxStack(6, 1),
|
||||||
|
memorySize: memoryStaticCall,
|
||||||
|
}
|
||||||
|
|
||||||
|
jt[DELEGATECALL] = &operation{
|
||||||
|
execute: opDelegateCall,
|
||||||
|
dynamicGas: gasDelegateCallEIP4762,
|
||||||
|
minStack: minStack(6, 1),
|
||||||
|
maxStack: maxStack(6, 1),
|
||||||
|
memorySize: memoryDelegateCall,
|
||||||
|
}
|
||||||
|
|
||||||
|
jt[PUSH1] = &operation{
|
||||||
|
execute: opPush1EIP4762,
|
||||||
|
constantGas: GasFastestStep,
|
||||||
|
minStack: minStack(0, 1),
|
||||||
|
maxStack: maxStack(0, 1),
|
||||||
|
}
|
||||||
|
for i := 1; i < 32; i++ {
|
||||||
|
jt[PUSH1+OpCode(i)] = &operation{
|
||||||
|
execute: makePushEIP4762(uint64(i+1), i+1),
|
||||||
|
constantGas: GasFastestStep,
|
||||||
|
minStack: minStack(0, 1),
|
||||||
|
maxStack: maxStack(0, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"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"
|
||||||
|
|
@ -42,6 +43,8 @@ type (
|
||||||
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
|
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
|
||||||
var precompiles map[common.Address]PrecompiledContract
|
var precompiles map[common.Address]PrecompiledContract
|
||||||
switch {
|
switch {
|
||||||
|
case evm.chainRules.IsVerkle:
|
||||||
|
precompiles = PrecompiledContractsVerkle
|
||||||
case evm.chainRules.IsPrague:
|
case evm.chainRules.IsPrague:
|
||||||
precompiles = PrecompiledContractsPrague
|
precompiles = PrecompiledContractsPrague
|
||||||
case evm.chainRules.IsCancun:
|
case evm.chainRules.IsCancun:
|
||||||
|
|
@ -89,6 +92,7 @@ type TxContext struct {
|
||||||
GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set)
|
GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set)
|
||||||
BlobHashes []common.Hash // Provides information for BLOBHASH
|
BlobHashes []common.Hash // Provides information for BLOBHASH
|
||||||
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
|
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
|
||||||
|
AccessEvents *state.AccessEvents // Capture all state accesses for this tx
|
||||||
}
|
}
|
||||||
|
|
||||||
// EVM is the Ethereum Virtual Machine base object and provides
|
// EVM is the Ethereum Virtual Machine base object and provides
|
||||||
|
|
@ -156,6 +160,9 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
|
||||||
// Reset resets the EVM with a new transaction context.Reset
|
// Reset resets the EVM with a new transaction context.Reset
|
||||||
// This is not threadsafe and should only be done very cautiously.
|
// This is not threadsafe and should only be done very cautiously.
|
||||||
func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) {
|
func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) {
|
||||||
|
if evm.chainRules.IsEIP4762 {
|
||||||
|
txCtx.AccessEvents = state.NewAccessEvents(statedb.PointCache())
|
||||||
|
}
|
||||||
evm.TxContext = txCtx
|
evm.TxContext = txCtx
|
||||||
evm.StateDB = statedb
|
evm.StateDB = statedb
|
||||||
}
|
}
|
||||||
|
|
@ -200,6 +207,16 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
p, isPrecompile := evm.precompile(addr)
|
p, isPrecompile := evm.precompile(addr)
|
||||||
|
|
||||||
if !evm.StateDB.Exist(addr) {
|
if !evm.StateDB.Exist(addr) {
|
||||||
|
if !isPrecompile && evm.chainRules.IsEIP4762 {
|
||||||
|
// add proof of absence to witness
|
||||||
|
wgas := evm.AccessEvents.AddAccount(addr, false)
|
||||||
|
if gas < wgas {
|
||||||
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
|
return nil, 0, ErrOutOfGas
|
||||||
|
}
|
||||||
|
gas -= wgas
|
||||||
|
}
|
||||||
|
|
||||||
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
|
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
|
||||||
// Calling a non-existing account, don't do anything.
|
// Calling a non-existing account, don't do anything.
|
||||||
return nil, gas, nil
|
return nil, gas, nil
|
||||||
|
|
@ -436,14 +453,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
|
||||||
if evm.chainRules.IsBerlin {
|
// creation fails, the access-list change should not be rolled back.
|
||||||
|
if evm.chainRules.IsEIP2929 {
|
||||||
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 +474,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()
|
||||||
|
if !evm.StateDB.Exist(address) {
|
||||||
evm.StateDB.CreateAccount(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)
|
||||||
}
|
}
|
||||||
|
|
@ -468,8 +496,18 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||||
// The contract is a scoped environment for this execution context only.
|
// The contract is a scoped environment for this execution context only.
|
||||||
contract := NewContract(caller, AccountRef(address), value, gas)
|
contract := NewContract(caller, AccountRef(address), value, gas)
|
||||||
contract.SetCodeOptionalHash(&address, codeAndHash)
|
contract.SetCodeOptionalHash(&address, codeAndHash)
|
||||||
|
contract.IsDeployment = true
|
||||||
|
|
||||||
|
// Charge the contract creation init gas in verkle mode
|
||||||
|
if evm.chainRules.IsEIP4762 {
|
||||||
|
if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) {
|
||||||
|
err = ErrOutOfGas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
ret, err = evm.interpreter.Run(contract, nil, false)
|
ret, err = evm.interpreter.Run(contract, nil, false)
|
||||||
|
}
|
||||||
|
|
||||||
// Check whether the max code size has been exceeded, assign err if the case.
|
// Check whether the max code size has been exceeded, assign err if the case.
|
||||||
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
|
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
|
||||||
|
|
@ -486,12 +524,25 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||||
// be stored due to not enough gas set an error and let it be handled
|
// be stored due to not enough gas set an error and let it be handled
|
||||||
// by the error checking condition below.
|
// by the error checking condition below.
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
if !evm.chainRules.IsEIP4762 {
|
||||||
createDataGas := uint64(len(ret)) * params.CreateDataGas
|
createDataGas := uint64(len(ret)) * params.CreateDataGas
|
||||||
if contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||||
evm.StateDB.SetCode(address, ret)
|
|
||||||
} else {
|
|
||||||
err = ErrCodeStoreOutOfGas
|
err = ErrCodeStoreOutOfGas
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Contract creation completed, touch the missing fields in the contract
|
||||||
|
if !contract.UseGas(evm.AccessEvents.AddAccount(address, true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) {
|
||||||
|
err = ErrCodeStoreOutOfGas
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) {
|
||||||
|
err = ErrCodeStoreOutOfGas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
evm.StateDB.SetCode(address, ret)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When an error was returned by the EVM or when setting the creation code
|
// When an error was returned by the EVM or when setting the creation code
|
||||||
|
|
|
||||||
|
|
@ -383,7 +383,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
||||||
} else if !evm.StateDB.Exist(address) {
|
} else if !evm.StateDB.Exist(address) {
|
||||||
gas += params.CallNewAccountGas
|
gas += params.CallNewAccountGas
|
||||||
}
|
}
|
||||||
if transfersValue {
|
if transfersValue && !evm.chainRules.IsEIP4762 {
|
||||||
gas += params.CallValueTransferGas
|
gas += params.CallValueTransferGas
|
||||||
}
|
}
|
||||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||||
|
|
@ -394,7 +394,14 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
||||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||||
return 0, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
if evm.chainRules.IsEIP4762 {
|
||||||
|
if transfersValue {
|
||||||
|
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
|
||||||
|
if overflow {
|
||||||
|
return 0, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
@ -402,6 +409,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
||||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||||
return 0, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -414,12 +422,22 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
|
||||||
gas uint64
|
gas uint64
|
||||||
overflow bool
|
overflow bool
|
||||||
)
|
)
|
||||||
if stack.Back(2).Sign() != 0 {
|
if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 {
|
||||||
gas += params.CallValueTransferGas
|
gas += params.CallValueTransferGas
|
||||||
}
|
}
|
||||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||||
return 0, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
if evm.chainRules.IsEIP4762 {
|
||||||
|
address := common.Address(stack.Back(1).Bytes20())
|
||||||
|
transfersValue := !stack.Back(2).IsZero()
|
||||||
|
if transfersValue {
|
||||||
|
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
|
||||||
|
if overflow {
|
||||||
|
return 0, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
|
||||||
|
|
@ -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.Clear()
|
|
||||||
} else {
|
|
||||||
z.AddMod(&x, &y, z)
|
z.AddMod(&x, &y, z)
|
||||||
}
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -363,9 +359,9 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
|
||||||
if overflow {
|
if overflow {
|
||||||
uint64CodeOffset = math.MaxUint64
|
uint64CodeOffset = math.MaxUint64
|
||||||
}
|
}
|
||||||
|
|
||||||
codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64())
|
codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64())
|
||||||
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
|
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -438,6 +434,7 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
|
||||||
num.Clear()
|
num.Clear()
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var upper, lower uint64
|
var upper, lower uint64
|
||||||
upper = interpreter.evm.Context.BlockNumber.Uint64()
|
upper = interpreter.evm.Context.BlockNumber.Uint64()
|
||||||
if upper < 257 {
|
if upper < 257 {
|
||||||
|
|
@ -587,6 +584,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
||||||
if interpreter.evm.chainRules.IsEIP150 {
|
if interpreter.evm.chainRules.IsEIP150 {
|
||||||
gas -= gas / 64
|
gas -= gas / 64
|
||||||
}
|
}
|
||||||
|
|
||||||
// reuse size int for stackvalue
|
// reuse size int for stackvalue
|
||||||
stackvalue := size
|
stackvalue := size
|
||||||
|
|
||||||
|
|
@ -627,6 +625,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
||||||
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
|
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
|
||||||
gas = scope.Contract.Gas
|
gas = scope.Contract.Gas
|
||||||
)
|
)
|
||||||
|
|
||||||
// Apply EIP150
|
// Apply EIP150
|
||||||
gas -= gas / 64
|
gas -= gas / 64
|
||||||
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
|
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
|
||||||
|
|
@ -641,7 +640,6 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
||||||
stackvalue.SetBytes(addr.Bytes())
|
stackvalue.SetBytes(addr.Bytes())
|
||||||
}
|
}
|
||||||
scope.Stack.push(&stackvalue)
|
scope.Stack.push(&stackvalue)
|
||||||
|
|
||||||
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||||
|
|
||||||
if suberr == ErrExecutionReverted {
|
if suberr == ErrExecutionReverted {
|
||||||
|
|
@ -900,6 +898,7 @@ func makePush(size uint64, pushByteSize int) executionFunc {
|
||||||
pushByteSize,
|
pushByteSize,
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
*pc += size
|
*pc += size
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -643,7 +643,7 @@ func BenchmarkOpKeccak256(bench *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreate2Addreses(t *testing.T) {
|
func TestCreate2Addresses(t *testing.T) {
|
||||||
type testcase struct {
|
type testcase struct {
|
||||||
origin string
|
origin string
|
||||||
salt string
|
salt string
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,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/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/trie/utils"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
@ -74,6 +76,10 @@ type StateDB interface {
|
||||||
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
|
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
|
||||||
// even if the feature/fork is not active yet
|
// even if the feature/fork is not active yet
|
||||||
AddSlotToAccessList(addr common.Address, slot common.Hash)
|
AddSlotToAccessList(addr common.Address, slot common.Hash)
|
||||||
|
|
||||||
|
// PointCache returns the point cache used in computations
|
||||||
|
PointCache() *utils.PointCache
|
||||||
|
|
||||||
Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
|
Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
|
||||||
|
|
||||||
RevertToSnapshot(int)
|
RevertToSnapshot(int)
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,9 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
|
||||||
// If jump table was not initialised we set the default one.
|
// If jump table was not initialised we set the default one.
|
||||||
var table *JumpTable
|
var table *JumpTable
|
||||||
switch {
|
switch {
|
||||||
|
case evm.chainRules.IsVerkle:
|
||||||
|
// TODO replace with proper instruction set when fork is specified
|
||||||
|
table = &verkleInstructionSet
|
||||||
case evm.chainRules.IsCancun:
|
case evm.chainRules.IsCancun:
|
||||||
table = &cancunInstructionSet
|
table = &cancunInstructionSet
|
||||||
case evm.chainRules.IsShanghai:
|
case evm.chainRules.IsShanghai:
|
||||||
|
|
@ -219,6 +222,14 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||||
// Capture pre-execution values for tracing.
|
// Capture pre-execution values for tracing.
|
||||||
logged, pcCopy, gasCopy = false, pc, contract.Gas
|
logged, pcCopy, gasCopy = false, pc, contract.Gas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if in.evm.chainRules.IsEIP4762 && !contract.IsDeployment {
|
||||||
|
// if the PC ends up in a new "chunk" of verkleized code, charge the
|
||||||
|
// associated costs.
|
||||||
|
contractAddr := contract.Address()
|
||||||
|
contract.Gas -= in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false)
|
||||||
|
}
|
||||||
|
|
||||||
// Get the operation from the jump table and validate the stack to ensure there are
|
// Get the operation from the jump table and validate the stack to ensure there are
|
||||||
// enough stack items available to perform the operation.
|
// enough stack items available to perform the operation.
|
||||||
op = contract.GetOp(pc)
|
op = contract.GetOp(pc)
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ var (
|
||||||
mergeInstructionSet = newMergeInstructionSet()
|
mergeInstructionSet = newMergeInstructionSet()
|
||||||
shanghaiInstructionSet = newShanghaiInstructionSet()
|
shanghaiInstructionSet = newShanghaiInstructionSet()
|
||||||
cancunInstructionSet = newCancunInstructionSet()
|
cancunInstructionSet = newCancunInstructionSet()
|
||||||
|
verkleInstructionSet = newVerkleInstructionSet()
|
||||||
)
|
)
|
||||||
|
|
||||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||||
|
|
@ -80,6 +81,12 @@ func validate(jt JumpTable) JumpTable {
|
||||||
return jt
|
return jt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newVerkleInstructionSet() JumpTable {
|
||||||
|
instructionSet := newCancunInstructionSet()
|
||||||
|
enable4762(&instructionSet)
|
||||||
|
return validate(instructionSet)
|
||||||
|
}
|
||||||
|
|
||||||
func newCancunInstructionSet() JumpTable {
|
func newCancunInstructionSet() JumpTable {
|
||||||
instructionSet := newShanghaiInstructionSet()
|
instructionSet := newShanghaiInstructionSet()
|
||||||
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)
|
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)
|
||||||
|
|
|
||||||
159
core/vm/operations_verkle.go
Normal file
159
core/vm/operations_verkle.go
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
// 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 vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true)
|
||||||
|
if gas == 0 {
|
||||||
|
gas = params.WarmStorageReadCostEIP2929
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false)
|
||||||
|
if gas == 0 {
|
||||||
|
gas = params.WarmStorageReadCostEIP2929
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
address := stack.peek().Bytes20()
|
||||||
|
gas := evm.AccessEvents.BalanceGas(address, false)
|
||||||
|
if gas == 0 {
|
||||||
|
gas = params.WarmStorageReadCostEIP2929
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
address := stack.peek().Bytes20()
|
||||||
|
if _, isPrecompile := evm.precompile(address); isPrecompile {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
gas := evm.AccessEvents.VersionGas(address, false)
|
||||||
|
gas += evm.AccessEvents.CodeSizeGas(address, false)
|
||||||
|
if gas == 0 {
|
||||||
|
gas = params.WarmStorageReadCostEIP2929
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
address := stack.peek().Bytes20()
|
||||||
|
if _, isPrecompile := evm.precompile(address); isPrecompile {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
gas := evm.AccessEvents.CodeHashGas(address, false)
|
||||||
|
if gas == 0 {
|
||||||
|
gas = params.WarmStorageReadCostEIP2929
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc {
|
||||||
|
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
gas, err := oldCalculator(evm, contract, stack, mem, memorySize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile {
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
witnessGas := evm.AccessEvents.MessageCallGas(contract.Address())
|
||||||
|
if witnessGas == 0 {
|
||||||
|
witnessGas = params.WarmStorageReadCostEIP2929
|
||||||
|
}
|
||||||
|
return witnessGas + gas, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
gasCallEIP4762 = makeCallVariantGasEIP4762(gasCall)
|
||||||
|
gasCallCodeEIP4762 = makeCallVariantGasEIP4762(gasCallCode)
|
||||||
|
gasStaticCallEIP4762 = makeCallVariantGasEIP4762(gasStaticCall)
|
||||||
|
gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall)
|
||||||
|
)
|
||||||
|
|
||||||
|
func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
beneficiaryAddr := common.Address(stack.peek().Bytes20())
|
||||||
|
if _, isPrecompile := evm.precompile(beneficiaryAddr); isPrecompile {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
contractAddr := contract.Address()
|
||||||
|
statelessGas := evm.AccessEvents.VersionGas(contractAddr, false)
|
||||||
|
statelessGas += evm.AccessEvents.CodeSizeGas(contractAddr, false)
|
||||||
|
statelessGas += evm.AccessEvents.BalanceGas(contractAddr, false)
|
||||||
|
if contractAddr != beneficiaryAddr {
|
||||||
|
statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr, false)
|
||||||
|
}
|
||||||
|
// Charge write costs if it transfers value
|
||||||
|
if evm.StateDB.GetBalance(contractAddr).Sign() != 0 {
|
||||||
|
statelessGas += evm.AccessEvents.BalanceGas(contractAddr, true)
|
||||||
|
if contractAddr != beneficiaryAddr {
|
||||||
|
statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return statelessGas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
gas, err := gasCodeCopy(evm, contract, stack, mem, memorySize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
codeOffset = stack.Back(1)
|
||||||
|
length = stack.Back(2)
|
||||||
|
)
|
||||||
|
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
||||||
|
if overflow {
|
||||||
|
uint64CodeOffset = math.MaxUint64
|
||||||
|
}
|
||||||
|
_, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64())
|
||||||
|
if !contract.IsDeployment {
|
||||||
|
gas += evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false)
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
// memory expansion first (dynamic part of pre-2929 implementation)
|
||||||
|
gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
addr := common.Address(stack.peek().Bytes20())
|
||||||
|
wgas := evm.AccessEvents.VersionGas(addr, false)
|
||||||
|
wgas += evm.AccessEvents.CodeSizeGas(addr, false)
|
||||||
|
if wgas == 0 {
|
||||||
|
wgas = params.WarmStorageReadCostEIP2929
|
||||||
|
}
|
||||||
|
var overflow bool
|
||||||
|
// We charge (cold-warm), since 'warm' is already charged as constantGas
|
||||||
|
if gas, overflow = math.SafeAdd(gas, wgas); overflow {
|
||||||
|
return 0, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
|
@ -203,8 +203,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
)
|
)
|
||||||
if config.VMTrace != "" {
|
if config.VMTrace != "" {
|
||||||
var traceConfig json.RawMessage
|
var traceConfig json.RawMessage
|
||||||
if config.VMTraceConfig != "" {
|
if config.VMTraceJsonConfig != "" {
|
||||||
traceConfig = json.RawMessage(config.VMTraceConfig)
|
traceConfig = json.RawMessage(config.VMTraceJsonConfig)
|
||||||
}
|
}
|
||||||
t, err := tracers.LiveDirectory.New(config.VMTrace, traceConfig)
|
t, err := tracers.LiveDirectory.New(config.VMTrace, traceConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -41,16 +41,13 @@ import (
|
||||||
var (
|
var (
|
||||||
MaxBlockFetch = 128 // Number of blocks to be fetched per retrieval request
|
MaxBlockFetch = 128 // Number of blocks to be fetched per retrieval request
|
||||||
MaxHeaderFetch = 192 // Number of block headers to be fetched per retrieval request
|
MaxHeaderFetch = 192 // Number of block headers to be fetched per retrieval request
|
||||||
MaxSkeletonSize = 128 // Number of header fetches needed for a skeleton assembly
|
|
||||||
MaxReceiptFetch = 256 // Number of transaction receipts to allow fetching per request
|
MaxReceiptFetch = 256 // Number of transaction receipts to allow fetching per request
|
||||||
|
|
||||||
maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection)
|
maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection)
|
||||||
maxHeadersProcess = 2048 // Number of header download results to import at once into the chain
|
maxHeadersProcess = 2048 // Number of header download results to import at once into the chain
|
||||||
maxResultsProcess = 2048 // Number of content download results to import at once into the chain
|
maxResultsProcess = 2048 // Number of content download results to import at once into the chain
|
||||||
fullMaxForkAncestry uint64 = params.FullImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it)
|
fullMaxForkAncestry uint64 = params.FullImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it)
|
||||||
lightMaxForkAncestry uint64 = params.LightImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it)
|
|
||||||
|
|
||||||
reorgProtThreshold = 48 // Threshold number of recent blocks to disable mini reorg protection
|
|
||||||
reorgProtHeaderDelay = 2 // Number of headers to delay delivering to cover mini reorgs
|
reorgProtHeaderDelay = 2 // Number of headers to delay delivering to cover mini reorgs
|
||||||
|
|
||||||
fsHeaderSafetyNet = 2048 // Number of headers to discard in case a chain violation is detected
|
fsHeaderSafetyNet = 2048 // Number of headers to discard in case a chain violation is detected
|
||||||
|
|
@ -60,23 +57,15 @@ var (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errBusy = errors.New("busy")
|
errBusy = errors.New("busy")
|
||||||
errUnknownPeer = errors.New("peer is unknown or unhealthy")
|
|
||||||
errBadPeer = errors.New("action from bad peer ignored")
|
errBadPeer = errors.New("action from bad peer ignored")
|
||||||
errStallingPeer = errors.New("peer is stalling")
|
|
||||||
errUnsyncedPeer = errors.New("unsynced peer")
|
|
||||||
errNoPeers = errors.New("no peers to keep download active")
|
|
||||||
errTimeout = errors.New("timeout")
|
errTimeout = errors.New("timeout")
|
||||||
errEmptyHeaderSet = errors.New("empty header set by peer")
|
|
||||||
errPeersUnavailable = errors.New("no peers available or all tried for download")
|
|
||||||
errInvalidAncestor = errors.New("retrieved ancestor is invalid")
|
|
||||||
errInvalidChain = errors.New("retrieved hash chain is invalid")
|
errInvalidChain = errors.New("retrieved hash chain is invalid")
|
||||||
errInvalidBody = errors.New("retrieved block body is invalid")
|
errInvalidBody = errors.New("retrieved block body is invalid")
|
||||||
errInvalidReceipt = errors.New("retrieved receipt is invalid")
|
errInvalidReceipt = errors.New("retrieved receipt is invalid")
|
||||||
errCancelStateFetch = errors.New("state data download canceled (requested)")
|
errCancelStateFetch = errors.New("state data download canceled (requested)")
|
||||||
errCancelContentProcessing = errors.New("content processing canceled (requested)")
|
errCancelContentProcessing = errors.New("content processing canceled (requested)")
|
||||||
errCanceled = errors.New("syncing canceled (requested)")
|
errCanceled = errors.New("syncing canceled (requested)")
|
||||||
errTooOld = errors.New("peer's protocol version too old")
|
|
||||||
errNoAncestorFound = errors.New("no common ancestor found")
|
|
||||||
errNoPivotHeader = errors.New("pivot header is not found")
|
errNoPivotHeader = errors.New("pivot header is not found")
|
||||||
ErrMergeTransition = errors.New("legacy sync reached the merge")
|
ErrMergeTransition = errors.New("legacy sync reached the merge")
|
||||||
)
|
)
|
||||||
|
|
@ -99,7 +88,6 @@ type Downloader struct {
|
||||||
mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
|
mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
|
||||||
mux *event.TypeMux // Event multiplexer to announce sync operation events
|
mux *event.TypeMux // Event multiplexer to announce sync operation events
|
||||||
|
|
||||||
genesis uint64 // Genesis block number to limit sync to (e.g. light client CHT)
|
|
||||||
queue *queue // Scheduler for selecting the hashes to download
|
queue *queue // Scheduler for selecting the hashes to download
|
||||||
peers *peerSet // Set of active peers from which download can proceed
|
peers *peerSet // Set of active peers from which download can proceed
|
||||||
|
|
||||||
|
|
@ -118,7 +106,6 @@ type Downloader struct {
|
||||||
badBlock badBlockFn // Reports a block as rejected by the chain
|
badBlock badBlockFn // Reports a block as rejected by the chain
|
||||||
|
|
||||||
// Status
|
// Status
|
||||||
synchroniseMock func(id string, hash common.Hash) error // Replacement for synchronise during testing
|
|
||||||
synchronising atomic.Bool
|
synchronising atomic.Bool
|
||||||
notified atomic.Bool
|
notified atomic.Bool
|
||||||
committed atomic.Bool
|
committed atomic.Bool
|
||||||
|
|
@ -138,7 +125,6 @@ type Downloader struct {
|
||||||
stateSyncStart chan *stateSync
|
stateSyncStart chan *stateSync
|
||||||
|
|
||||||
// Cancellation and termination
|
// Cancellation and termination
|
||||||
cancelPeer string // Identifier of the peer currently being used as the master (cancel on drop)
|
|
||||||
cancelCh chan struct{} // Channel to cancel mid-flight syncs
|
cancelCh chan struct{} // Channel to cancel mid-flight syncs
|
||||||
cancelLock sync.RWMutex // Lock to protect the cancel channel and peer in delivers
|
cancelLock sync.RWMutex // Lock to protect the cancel channel and peer in delivers
|
||||||
cancelWg sync.WaitGroup // Make sure all fetcher goroutines have exited.
|
cancelWg sync.WaitGroup // Make sure all fetcher goroutines have exited.
|
||||||
|
|
@ -147,7 +133,6 @@ type Downloader struct {
|
||||||
quitLock sync.Mutex // Lock to prevent double closes
|
quitLock sync.Mutex // Lock to prevent double closes
|
||||||
|
|
||||||
// Testing hooks
|
// Testing hooks
|
||||||
syncInitHook func(uint64, uint64) // Method to call upon initiating a new sync run
|
|
||||||
bodyFetchHook func([]*types.Header) // Method to call upon starting a block body fetch
|
bodyFetchHook func([]*types.Header) // Method to call upon starting a block body fetch
|
||||||
receiptFetchHook func([]*types.Header) // Method to call upon starting a receipt fetch
|
receiptFetchHook func([]*types.Header) // Method to call upon starting a receipt fetch
|
||||||
chainInsertHook func([]*fetchResult) // Method to call upon inserting a chain of blocks (possibly in multiple invocations)
|
chainInsertHook func([]*fetchResult) // Method to call upon inserting a chain of blocks (possibly in multiple invocations)
|
||||||
|
|
@ -326,39 +311,10 @@ func (d *Downloader) UnregisterPeer(id string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LegacySync tries to sync up our local block chain with a remote peer, both
|
|
||||||
// adding various sanity checks as well as wrapping it with various log entries.
|
|
||||||
func (d *Downloader) LegacySync(id string, head common.Hash, td, ttd *big.Int, mode SyncMode) error {
|
|
||||||
err := d.synchronise(id, head, td, ttd, mode, false, nil)
|
|
||||||
|
|
||||||
switch err {
|
|
||||||
case nil, errBusy, errCanceled:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if errors.Is(err, errInvalidChain) || errors.Is(err, errBadPeer) || errors.Is(err, errTimeout) ||
|
|
||||||
errors.Is(err, errStallingPeer) || errors.Is(err, errUnsyncedPeer) || errors.Is(err, errEmptyHeaderSet) ||
|
|
||||||
errors.Is(err, errPeersUnavailable) || errors.Is(err, errTooOld) || errors.Is(err, errInvalidAncestor) {
|
|
||||||
log.Warn("Synchronisation failed, dropping peer", "peer", id, "err", err)
|
|
||||||
if d.dropPeer == nil {
|
|
||||||
// The dropPeer method is nil when `--copydb` is used for a local copy.
|
|
||||||
// Timeouts can occur if e.g. compaction hits at the wrong time, and can be ignored
|
|
||||||
log.Warn("Downloader wants to drop peer, but peerdrop-function is not set", "peer", id)
|
|
||||||
} else {
|
|
||||||
d.dropPeer(id)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if errors.Is(err, ErrMergeTransition) {
|
|
||||||
return err // This is an expected fault, don't keep printing it in a spin-loop
|
|
||||||
}
|
|
||||||
log.Warn("Synchronisation failed, retrying", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// synchronise will select the peer and use it for synchronising. If an empty string is given
|
// synchronise will select the peer and use it for synchronising. If an empty string is given
|
||||||
// it will use the best peer possible and synchronize if its TD is higher than our own. If any of the
|
// it will use the best peer possible and synchronize if its TD is higher than our own. If any of the
|
||||||
// checks fail an error will be returned. This method is synchronous
|
// checks fail an error will be returned. This method is synchronous
|
||||||
func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int, mode SyncMode, beaconMode bool, beaconPing chan struct{}) error {
|
func (d *Downloader) synchronise(mode SyncMode, beaconPing chan struct{}) error {
|
||||||
// The beacon header syncer is async. It will start this synchronization and
|
// The beacon header syncer is async. It will start this synchronization and
|
||||||
// will continue doing other tasks. However, if synchronization needs to be
|
// will continue doing other tasks. However, if synchronization needs to be
|
||||||
// cancelled, the syncer needs to know if we reached the startup point (and
|
// cancelled, the syncer needs to know if we reached the startup point (and
|
||||||
|
|
@ -373,10 +329,6 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int,
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
// Mock out the synchronisation if testing
|
|
||||||
if d.synchroniseMock != nil {
|
|
||||||
return d.synchroniseMock(id, hash)
|
|
||||||
}
|
|
||||||
// Make sure only one goroutine is ever allowed past this point at once
|
// Make sure only one goroutine is ever allowed past this point at once
|
||||||
if !d.synchronising.CompareAndSwap(false, true) {
|
if !d.synchronising.CompareAndSwap(false, true) {
|
||||||
return errBusy
|
return errBusy
|
||||||
|
|
@ -424,7 +376,6 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int,
|
||||||
// Create cancel channel for aborting mid-flight and mark the master peer
|
// Create cancel channel for aborting mid-flight and mark the master peer
|
||||||
d.cancelLock.Lock()
|
d.cancelLock.Lock()
|
||||||
d.cancelCh = make(chan struct{})
|
d.cancelCh = make(chan struct{})
|
||||||
d.cancelPeer = id
|
|
||||||
d.cancelLock.Unlock()
|
d.cancelLock.Unlock()
|
||||||
|
|
||||||
defer d.Cancel() // No matter what, we can't leave the cancel channel open
|
defer d.Cancel() // No matter what, we can't leave the cancel channel open
|
||||||
|
|
@ -432,27 +383,19 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int,
|
||||||
// Atomically set the requested sync mode
|
// Atomically set the requested sync mode
|
||||||
d.mode.Store(uint32(mode))
|
d.mode.Store(uint32(mode))
|
||||||
|
|
||||||
// Retrieve the origin peer and initiate the downloading process
|
|
||||||
var p *peerConnection
|
|
||||||
if !beaconMode { // Beacon mode doesn't need a peer to sync from
|
|
||||||
p = d.peers.Peer(id)
|
|
||||||
if p == nil {
|
|
||||||
return errUnknownPeer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if beaconPing != nil {
|
if beaconPing != nil {
|
||||||
close(beaconPing)
|
close(beaconPing)
|
||||||
}
|
}
|
||||||
return d.syncWithPeer(p, hash, td, ttd, beaconMode)
|
return d.syncToHead()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) getMode() SyncMode {
|
func (d *Downloader) getMode() SyncMode {
|
||||||
return SyncMode(d.mode.Load())
|
return SyncMode(d.mode.Load())
|
||||||
}
|
}
|
||||||
|
|
||||||
// syncWithPeer starts a block synchronization based on the hash chain from the
|
// syncToHead starts a block synchronization based on the hash chain from
|
||||||
// specified peer and head hash.
|
// the specified head hash.
|
||||||
func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *big.Int, beaconMode bool) (err error) {
|
func (d *Downloader) syncToHead() (err error) {
|
||||||
d.mux.Post(StartEvent{})
|
d.mux.Post(StartEvent{})
|
||||||
defer func() {
|
defer func() {
|
||||||
// reset on error
|
// reset on error
|
||||||
|
|
@ -465,25 +408,13 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
|
||||||
}()
|
}()
|
||||||
mode := d.getMode()
|
mode := d.getMode()
|
||||||
|
|
||||||
if !beaconMode {
|
|
||||||
log.Debug("Synchronising with the network", "peer", p.id, "eth", p.version, "head", hash, "td", td, "mode", mode)
|
|
||||||
} else {
|
|
||||||
log.Debug("Backfilling with the network", "mode", mode)
|
log.Debug("Backfilling with the network", "mode", mode)
|
||||||
}
|
|
||||||
defer func(start time.Time) {
|
defer func(start time.Time) {
|
||||||
log.Debug("Synchronisation terminated", "elapsed", common.PrettyDuration(time.Since(start)))
|
log.Debug("Synchronisation terminated", "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
// Look up the sync boundaries: the common ancestor and the target block
|
// Look up the sync boundaries: the common ancestor and the target block
|
||||||
var latest, pivot, final *types.Header
|
var latest, pivot, final *types.Header
|
||||||
if !beaconMode {
|
|
||||||
// In legacy mode, use the master peer to retrieve the headers from
|
|
||||||
latest, pivot, err = d.fetchHead(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// In beacon mode, use the skeleton chain to retrieve the headers from
|
|
||||||
latest, _, final, err = d.skeleton.Bounds()
|
latest, _, final, err = d.skeleton.Bounds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -512,7 +443,6 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
|
||||||
return errNoPivotHeader
|
return errNoPivotHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// If no pivot block was returned, the head is below the min full block
|
// If no pivot block was returned, the head is below the min full block
|
||||||
// threshold (i.e. new chain). In that case we won't really snap sync
|
// threshold (i.e. new chain). In that case we won't really snap sync
|
||||||
// anyway, but still need a valid pivot block to avoid some code hitting
|
// anyway, but still need a valid pivot block to avoid some code hitting
|
||||||
|
|
@ -522,20 +452,11 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
|
||||||
}
|
}
|
||||||
height := latest.Number.Uint64()
|
height := latest.Number.Uint64()
|
||||||
|
|
||||||
var origin uint64
|
|
||||||
if !beaconMode {
|
|
||||||
// In legacy mode, reach out to the network and find the ancestor
|
|
||||||
origin, err = d.findAncestor(p, latest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// In beacon mode, use the skeleton chain for the ancestor lookup
|
// In beacon mode, use the skeleton chain for the ancestor lookup
|
||||||
origin, err = d.findBeaconAncestor()
|
origin, err := d.findBeaconAncestor()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
d.syncStatsLock.Lock()
|
d.syncStatsLock.Lock()
|
||||||
if d.syncStatsChainHeight <= origin || d.syncStatsChainOrigin > origin {
|
if d.syncStatsChainHeight <= origin || d.syncStatsChainOrigin > origin {
|
||||||
d.syncStatsChainOrigin = origin
|
d.syncStatsChainOrigin = origin
|
||||||
|
|
@ -577,7 +498,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
|
||||||
// the ancientLimit through that. Otherwise calculate the ancient limit through
|
// the ancientLimit through that. Otherwise calculate the ancient limit through
|
||||||
// the advertised height of the remote peer. This most is mostly a fallback for
|
// the advertised height of the remote peer. This most is mostly a fallback for
|
||||||
// legacy networks, but should eventually be dropped. TODO(karalabe).
|
// legacy networks, but should eventually be dropped. TODO(karalabe).
|
||||||
if beaconMode {
|
//
|
||||||
// Beacon sync, use the latest finalized block as the ancient limit
|
// Beacon sync, use the latest finalized block as the ancient limit
|
||||||
// or a reasonable height if no finalized block is yet announced.
|
// or a reasonable height if no finalized block is yet announced.
|
||||||
if final != nil {
|
if final != nil {
|
||||||
|
|
@ -587,15 +508,6 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
|
||||||
} else {
|
} else {
|
||||||
d.ancientLimit = 0
|
d.ancientLimit = 0
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Legacy sync, use the best announcement we have from the remote peer.
|
|
||||||
// TODO(karalabe): Drop this pathway.
|
|
||||||
if height > fullMaxForkAncestry+1 {
|
|
||||||
d.ancientLimit = height - fullMaxForkAncestry - 1
|
|
||||||
} else {
|
|
||||||
d.ancientLimit = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
frozen, _ := d.stateDB.Ancients() // Ignore the error here since light client can also hit here.
|
frozen, _ := d.stateDB.Ancients() // Ignore the error here since light client can also hit here.
|
||||||
|
|
||||||
// If a part of blockchain data has already been written into active store,
|
// If a part of blockchain data has already been written into active store,
|
||||||
|
|
@ -616,22 +528,13 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
|
||||||
}
|
}
|
||||||
// Initiate the sync using a concurrent header and content retrieval algorithm
|
// Initiate the sync using a concurrent header and content retrieval algorithm
|
||||||
d.queue.Prepare(origin+1, mode)
|
d.queue.Prepare(origin+1, mode)
|
||||||
if d.syncInitHook != nil {
|
|
||||||
d.syncInitHook(origin, height)
|
|
||||||
}
|
|
||||||
var headerFetcher func() error
|
|
||||||
if !beaconMode {
|
|
||||||
// In legacy mode, headers are retrieved from the network
|
|
||||||
headerFetcher = func() error { return d.fetchHeaders(p, origin+1, latest.Number.Uint64()) }
|
|
||||||
} else {
|
|
||||||
// In beacon mode, headers are served by the skeleton syncer
|
// In beacon mode, headers are served by the skeleton syncer
|
||||||
headerFetcher = func() error { return d.fetchBeaconHeaders(origin + 1) }
|
|
||||||
}
|
|
||||||
fetchers := []func() error{
|
fetchers := []func() error{
|
||||||
headerFetcher, // Headers are always retrieved
|
func() error { return d.fetchHeaders(origin + 1) }, // Headers are always retrieved
|
||||||
func() error { return d.fetchBodies(origin+1, beaconMode) }, // Bodies are retrieved during normal and snap sync
|
func() error { return d.fetchBodies(origin + 1) }, // Bodies are retrieved during normal and snap sync
|
||||||
func() error { return d.fetchReceipts(origin+1, beaconMode) }, // Receipts are retrieved during snap sync
|
func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during snap sync
|
||||||
func() error { return d.processHeaders(origin+1, td, ttd, beaconMode) },
|
func() error { return d.processHeaders(origin + 1) },
|
||||||
}
|
}
|
||||||
if mode == SnapSync {
|
if mode == SnapSync {
|
||||||
d.pivotLock.Lock()
|
d.pivotLock.Lock()
|
||||||
|
|
@ -640,7 +543,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
|
||||||
|
|
||||||
fetchers = append(fetchers, func() error { return d.processSnapSyncContent() })
|
fetchers = append(fetchers, func() error { return d.processSnapSyncContent() })
|
||||||
} else if mode == FullSync {
|
} else if mode == FullSync {
|
||||||
fetchers = append(fetchers, func() error { return d.processFullSyncContent(ttd, beaconMode) })
|
fetchers = append(fetchers, func() error { return d.processFullSyncContent() })
|
||||||
}
|
}
|
||||||
return d.spawnSync(fetchers)
|
return d.spawnSync(fetchers)
|
||||||
}
|
}
|
||||||
|
|
@ -719,540 +622,12 @@ func (d *Downloader) Terminate() {
|
||||||
d.Cancel()
|
d.Cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchHead retrieves the head header and prior pivot block (if available) from
|
|
||||||
// a remote peer.
|
|
||||||
func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *types.Header, err error) {
|
|
||||||
p.log.Debug("Retrieving remote chain head")
|
|
||||||
mode := d.getMode()
|
|
||||||
|
|
||||||
// Request the advertised remote head block and wait for the response
|
|
||||||
latest, _ := p.peer.Head()
|
|
||||||
fetch := 1
|
|
||||||
if mode == SnapSync {
|
|
||||||
fetch = 2 // head + pivot headers
|
|
||||||
}
|
|
||||||
headers, hashes, err := d.fetchHeadersByHash(p, latest, fetch, fsMinFullBlocks-1, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
// Make sure the peer gave us at least one and at most the requested headers
|
|
||||||
if len(headers) == 0 || len(headers) > fetch {
|
|
||||||
return nil, nil, fmt.Errorf("%w: returned headers %d != requested %d", errBadPeer, len(headers), fetch)
|
|
||||||
}
|
|
||||||
// The first header needs to be the head, validate against the request. If
|
|
||||||
// only 1 header was returned, make sure there's no pivot or there was not
|
|
||||||
// one requested.
|
|
||||||
head = headers[0]
|
|
||||||
if len(headers) == 1 {
|
|
||||||
if mode == SnapSync && head.Number.Uint64() > uint64(fsMinFullBlocks) {
|
|
||||||
return nil, nil, fmt.Errorf("%w: no pivot included along head header", errBadPeer)
|
|
||||||
}
|
|
||||||
p.log.Debug("Remote head identified, no pivot", "number", head.Number, "hash", hashes[0])
|
|
||||||
return head, nil, nil
|
|
||||||
}
|
|
||||||
// At this point we have 2 headers in total and the first is the
|
|
||||||
// validated head of the chain. Check the pivot number and return,
|
|
||||||
pivot = headers[1]
|
|
||||||
if pivot.Number.Uint64() != head.Number.Uint64()-uint64(fsMinFullBlocks) {
|
|
||||||
return nil, nil, fmt.Errorf("%w: remote pivot %d != requested %d", errInvalidChain, pivot.Number, head.Number.Uint64()-uint64(fsMinFullBlocks))
|
|
||||||
}
|
|
||||||
return head, pivot, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculateRequestSpan calculates what headers to request from a peer when trying to determine the
|
|
||||||
// common ancestor.
|
|
||||||
// It returns parameters to be used for peer.RequestHeadersByNumber:
|
|
||||||
//
|
|
||||||
// from - starting block number
|
|
||||||
// count - number of headers to request
|
|
||||||
// skip - number of headers to skip
|
|
||||||
//
|
|
||||||
// and also returns 'max', the last block which is expected to be returned by the remote peers,
|
|
||||||
// given the (from,count,skip)
|
|
||||||
func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, uint64) {
|
|
||||||
var (
|
|
||||||
from int
|
|
||||||
count int
|
|
||||||
MaxCount = MaxHeaderFetch / 16
|
|
||||||
)
|
|
||||||
// requestHead is the highest block that we will ask for. If requestHead is not offset,
|
|
||||||
// the highest block that we will get is 16 blocks back from head, which means we
|
|
||||||
// will fetch 14 or 15 blocks unnecessarily in the case the height difference
|
|
||||||
// between us and the peer is 1-2 blocks, which is most common
|
|
||||||
requestHead := int(remoteHeight) - 1
|
|
||||||
if requestHead < 0 {
|
|
||||||
requestHead = 0
|
|
||||||
}
|
|
||||||
// requestBottom is the lowest block we want included in the query
|
|
||||||
// Ideally, we want to include the one just below our own head
|
|
||||||
requestBottom := int(localHeight - 1)
|
|
||||||
if requestBottom < 0 {
|
|
||||||
requestBottom = 0
|
|
||||||
}
|
|
||||||
totalSpan := requestHead - requestBottom
|
|
||||||
span := 1 + totalSpan/MaxCount
|
|
||||||
if span < 2 {
|
|
||||||
span = 2
|
|
||||||
}
|
|
||||||
if span > 16 {
|
|
||||||
span = 16
|
|
||||||
}
|
|
||||||
|
|
||||||
count = 1 + totalSpan/span
|
|
||||||
if count > MaxCount {
|
|
||||||
count = MaxCount
|
|
||||||
}
|
|
||||||
if count < 2 {
|
|
||||||
count = 2
|
|
||||||
}
|
|
||||||
from = requestHead - (count-1)*span
|
|
||||||
if from < 0 {
|
|
||||||
from = 0
|
|
||||||
}
|
|
||||||
max := from + (count-1)*span
|
|
||||||
return int64(from), count, span - 1, uint64(max)
|
|
||||||
}
|
|
||||||
|
|
||||||
// findAncestor tries to locate the common ancestor link of the local chain and
|
|
||||||
// a remote peers blockchain. In the general case when our node was in sync and
|
|
||||||
// on the correct chain, checking the top N links should already get us a match.
|
|
||||||
// In the rare scenario when we ended up on a long reorganisation (i.e. none of
|
|
||||||
// the head links match), we do a binary search to find the common ancestor.
|
|
||||||
func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) (uint64, error) {
|
|
||||||
// Figure out the valid ancestor range to prevent rewrite attacks
|
|
||||||
var (
|
|
||||||
floor = int64(-1)
|
|
||||||
localHeight uint64
|
|
||||||
remoteHeight = remoteHeader.Number.Uint64()
|
|
||||||
)
|
|
||||||
mode := d.getMode()
|
|
||||||
switch mode {
|
|
||||||
case FullSync:
|
|
||||||
localHeight = d.blockchain.CurrentBlock().Number.Uint64()
|
|
||||||
case SnapSync:
|
|
||||||
localHeight = d.blockchain.CurrentSnapBlock().Number.Uint64()
|
|
||||||
default:
|
|
||||||
localHeight = d.lightchain.CurrentHeader().Number.Uint64()
|
|
||||||
}
|
|
||||||
p.log.Debug("Looking for common ancestor", "local", localHeight, "remote", remoteHeight)
|
|
||||||
|
|
||||||
// Recap floor value for binary search
|
|
||||||
maxForkAncestry := fullMaxForkAncestry
|
|
||||||
if d.getMode() == LightSync {
|
|
||||||
maxForkAncestry = lightMaxForkAncestry
|
|
||||||
}
|
|
||||||
if localHeight >= maxForkAncestry {
|
|
||||||
// We're above the max reorg threshold, find the earliest fork point
|
|
||||||
floor = int64(localHeight - maxForkAncestry)
|
|
||||||
}
|
|
||||||
// If we're doing a light sync, ensure the floor doesn't go below the CHT, as
|
|
||||||
// all headers before that point will be missing.
|
|
||||||
if mode == LightSync {
|
|
||||||
// If we don't know the current CHT position, find it
|
|
||||||
if d.genesis == 0 {
|
|
||||||
header := d.lightchain.CurrentHeader()
|
|
||||||
for header != nil {
|
|
||||||
d.genesis = header.Number.Uint64()
|
|
||||||
if floor >= int64(d.genesis)-1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
header = d.lightchain.GetHeaderByHash(header.ParentHash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We already know the "genesis" block number, cap floor to that
|
|
||||||
if floor < int64(d.genesis)-1 {
|
|
||||||
floor = int64(d.genesis) - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ancestor, err := d.findAncestorSpanSearch(p, mode, remoteHeight, localHeight, floor)
|
|
||||||
if err == nil {
|
|
||||||
return ancestor, nil
|
|
||||||
}
|
|
||||||
// The returned error was not nil.
|
|
||||||
// If the error returned does not reflect that a common ancestor was not found, return it.
|
|
||||||
// If the error reflects that a common ancestor was not found, continue to binary search,
|
|
||||||
// where the error value will be reassigned.
|
|
||||||
if !errors.Is(err, errNoAncestorFound) {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ancestor, err = d.findAncestorBinarySearch(p, mode, remoteHeight, floor)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return ancestor, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, remoteHeight, localHeight uint64, floor int64) (uint64, error) {
|
|
||||||
from, count, skip, max := calculateRequestSpan(remoteHeight, localHeight)
|
|
||||||
|
|
||||||
p.log.Trace("Span searching for common ancestor", "count", count, "from", from, "skip", skip)
|
|
||||||
headers, hashes, err := d.fetchHeadersByNumber(p, uint64(from), count, skip, false)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
// Wait for the remote response to the head fetch
|
|
||||||
number, hash := uint64(0), common.Hash{}
|
|
||||||
|
|
||||||
// Make sure the peer actually gave something valid
|
|
||||||
if len(headers) == 0 {
|
|
||||||
p.log.Warn("Empty head header set")
|
|
||||||
return 0, errEmptyHeaderSet
|
|
||||||
}
|
|
||||||
// Make sure the peer's reply conforms to the request
|
|
||||||
for i, header := range headers {
|
|
||||||
expectNumber := from + int64(i)*int64(skip+1)
|
|
||||||
if number := header.Number.Int64(); number != expectNumber {
|
|
||||||
p.log.Warn("Head headers broke chain ordering", "index", i, "requested", expectNumber, "received", number)
|
|
||||||
return 0, fmt.Errorf("%w: %v", errInvalidChain, errors.New("head headers broke chain ordering"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check if a common ancestor was found
|
|
||||||
for i := len(headers) - 1; i >= 0; i-- {
|
|
||||||
// Skip any headers that underflow/overflow our requested set
|
|
||||||
if headers[i].Number.Int64() < from || headers[i].Number.Uint64() > max {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Otherwise check if we already know the header or not
|
|
||||||
h := hashes[i]
|
|
||||||
n := headers[i].Number.Uint64()
|
|
||||||
|
|
||||||
var known bool
|
|
||||||
switch mode {
|
|
||||||
case FullSync:
|
|
||||||
known = d.blockchain.HasBlock(h, n)
|
|
||||||
case SnapSync:
|
|
||||||
known = d.blockchain.HasFastBlock(h, n)
|
|
||||||
default:
|
|
||||||
known = d.lightchain.HasHeader(h, n)
|
|
||||||
}
|
|
||||||
if known {
|
|
||||||
number, hash = n, h
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the head fetch already found an ancestor, return
|
|
||||||
if hash != (common.Hash{}) {
|
|
||||||
if int64(number) <= floor {
|
|
||||||
p.log.Warn("Ancestor below allowance", "number", number, "hash", hash, "allowance", floor)
|
|
||||||
return 0, errInvalidAncestor
|
|
||||||
}
|
|
||||||
p.log.Debug("Found common ancestor", "number", number, "hash", hash)
|
|
||||||
return number, nil
|
|
||||||
}
|
|
||||||
return 0, errNoAncestorFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, remoteHeight uint64, floor int64) (uint64, error) {
|
|
||||||
hash := common.Hash{}
|
|
||||||
|
|
||||||
// Ancestor not found, we need to binary search over our chain
|
|
||||||
start, end := uint64(0), remoteHeight
|
|
||||||
if floor > 0 {
|
|
||||||
start = uint64(floor)
|
|
||||||
}
|
|
||||||
p.log.Trace("Binary searching for common ancestor", "start", start, "end", end)
|
|
||||||
|
|
||||||
for start+1 < end {
|
|
||||||
// Split our chain interval in two, and request the hash to cross check
|
|
||||||
check := (start + end) / 2
|
|
||||||
|
|
||||||
headers, hashes, err := d.fetchHeadersByNumber(p, check, 1, 0, false)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
// Make sure the peer actually gave something valid
|
|
||||||
if len(headers) != 1 {
|
|
||||||
p.log.Warn("Multiple headers for single request", "headers", len(headers))
|
|
||||||
return 0, fmt.Errorf("%w: multiple headers (%d) for single request", errBadPeer, len(headers))
|
|
||||||
}
|
|
||||||
// Modify the search interval based on the response
|
|
||||||
h := hashes[0]
|
|
||||||
n := headers[0].Number.Uint64()
|
|
||||||
|
|
||||||
var known bool
|
|
||||||
switch mode {
|
|
||||||
case FullSync:
|
|
||||||
known = d.blockchain.HasBlock(h, n)
|
|
||||||
case SnapSync:
|
|
||||||
known = d.blockchain.HasFastBlock(h, n)
|
|
||||||
default:
|
|
||||||
known = d.lightchain.HasHeader(h, n)
|
|
||||||
}
|
|
||||||
if !known {
|
|
||||||
end = check
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
header := d.lightchain.GetHeaderByHash(h) // Independent of sync mode, header surely exists
|
|
||||||
if header.Number.Uint64() != check {
|
|
||||||
p.log.Warn("Received non requested header", "number", header.Number, "hash", header.Hash(), "request", check)
|
|
||||||
return 0, fmt.Errorf("%w: non-requested header (%d)", errBadPeer, header.Number)
|
|
||||||
}
|
|
||||||
start = check
|
|
||||||
hash = h
|
|
||||||
}
|
|
||||||
// Ensure valid ancestry and return
|
|
||||||
if int64(start) <= floor {
|
|
||||||
p.log.Warn("Ancestor below allowance", "number", start, "hash", hash, "allowance", floor)
|
|
||||||
return 0, errInvalidAncestor
|
|
||||||
}
|
|
||||||
p.log.Debug("Found common ancestor", "number", start, "hash", hash)
|
|
||||||
return start, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetchHeaders keeps retrieving headers concurrently from the number
|
|
||||||
// requested, until no more are returned, potentially throttling on the way. To
|
|
||||||
// facilitate concurrency but still protect against malicious nodes sending bad
|
|
||||||
// headers, we construct a header chain skeleton using the "origin" peer we are
|
|
||||||
// syncing with, and fill in the missing headers using anyone else. Headers from
|
|
||||||
// other peers are only accepted if they map cleanly to the skeleton. If no one
|
|
||||||
// can fill in the skeleton - not even the origin peer - it's assumed invalid and
|
|
||||||
// the origin is dropped.
|
|
||||||
func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, head uint64) error {
|
|
||||||
p.log.Debug("Directing header downloads", "origin", from)
|
|
||||||
defer p.log.Debug("Header download terminated")
|
|
||||||
|
|
||||||
// Start pulling the header chain skeleton until all is done
|
|
||||||
var (
|
|
||||||
skeleton = true // Skeleton assembly phase or finishing up
|
|
||||||
pivoting = false // Whether the next request is pivot verification
|
|
||||||
ancestor = from
|
|
||||||
mode = d.getMode()
|
|
||||||
)
|
|
||||||
for {
|
|
||||||
// Pull the next batch of headers, it either:
|
|
||||||
// - Pivot check to see if the chain moved too far
|
|
||||||
// - Skeleton retrieval to permit concurrent header fetches
|
|
||||||
// - Full header retrieval if we're near the chain head
|
|
||||||
var (
|
|
||||||
headers []*types.Header
|
|
||||||
hashes []common.Hash
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
switch {
|
|
||||||
case pivoting:
|
|
||||||
d.pivotLock.RLock()
|
|
||||||
pivot := d.pivotHeader.Number.Uint64()
|
|
||||||
d.pivotLock.RUnlock()
|
|
||||||
|
|
||||||
p.log.Trace("Fetching next pivot header", "number", pivot+uint64(fsMinFullBlocks))
|
|
||||||
headers, hashes, err = d.fetchHeadersByNumber(p, pivot+uint64(fsMinFullBlocks), 2, fsMinFullBlocks-9, false) // move +64 when it's 2x64-8 deep
|
|
||||||
|
|
||||||
case skeleton:
|
|
||||||
p.log.Trace("Fetching skeleton headers", "count", MaxHeaderFetch, "from", from)
|
|
||||||
headers, hashes, err = d.fetchHeadersByNumber(p, from+uint64(MaxHeaderFetch)-1, MaxSkeletonSize, MaxHeaderFetch-1, false)
|
|
||||||
|
|
||||||
default:
|
|
||||||
p.log.Trace("Fetching full headers", "count", MaxHeaderFetch, "from", from)
|
|
||||||
headers, hashes, err = d.fetchHeadersByNumber(p, from, MaxHeaderFetch, 0, false)
|
|
||||||
}
|
|
||||||
switch err {
|
|
||||||
case nil:
|
|
||||||
// Headers retrieved, continue with processing
|
|
||||||
|
|
||||||
case errCanceled:
|
|
||||||
// Sync cancelled, no issue, propagate up
|
|
||||||
return err
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Header retrieval either timed out, or the peer failed in some strange way
|
|
||||||
// (e.g. disconnect). Consider the master peer bad and drop
|
|
||||||
d.dropPeer(p.id)
|
|
||||||
|
|
||||||
// Finish the sync gracefully instead of dumping the gathered data though
|
|
||||||
for _, ch := range []chan bool{d.queue.blockWakeCh, d.queue.receiptWakeCh} {
|
|
||||||
select {
|
|
||||||
case ch <- false:
|
|
||||||
case <-d.cancelCh:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case d.headerProcCh <- nil:
|
|
||||||
case <-d.cancelCh:
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%w: header request failed: %v", errBadPeer, err)
|
|
||||||
}
|
|
||||||
// If the pivot is being checked, move if it became stale and run the real retrieval
|
|
||||||
var pivot uint64
|
|
||||||
|
|
||||||
d.pivotLock.RLock()
|
|
||||||
if d.pivotHeader != nil {
|
|
||||||
pivot = d.pivotHeader.Number.Uint64()
|
|
||||||
}
|
|
||||||
d.pivotLock.RUnlock()
|
|
||||||
|
|
||||||
if pivoting {
|
|
||||||
if len(headers) == 2 {
|
|
||||||
if have, want := headers[0].Number.Uint64(), pivot+uint64(fsMinFullBlocks); have != want {
|
|
||||||
log.Warn("Peer sent invalid next pivot", "have", have, "want", want)
|
|
||||||
return fmt.Errorf("%w: next pivot number %d != requested %d", errInvalidChain, have, want)
|
|
||||||
}
|
|
||||||
if have, want := headers[1].Number.Uint64(), pivot+2*uint64(fsMinFullBlocks)-8; have != want {
|
|
||||||
log.Warn("Peer sent invalid pivot confirmer", "have", have, "want", want)
|
|
||||||
return fmt.Errorf("%w: next pivot confirmer number %d != requested %d", errInvalidChain, have, want)
|
|
||||||
}
|
|
||||||
log.Warn("Pivot seemingly stale, moving", "old", pivot, "new", headers[0].Number)
|
|
||||||
pivot = headers[0].Number.Uint64()
|
|
||||||
|
|
||||||
d.pivotLock.Lock()
|
|
||||||
d.pivotHeader = headers[0]
|
|
||||||
d.pivotLock.Unlock()
|
|
||||||
|
|
||||||
// Write out the pivot into the database so a rollback beyond
|
|
||||||
// it will reenable snap sync and update the state root that
|
|
||||||
// the state syncer will be downloading.
|
|
||||||
rawdb.WriteLastPivotNumber(d.stateDB, pivot)
|
|
||||||
}
|
|
||||||
// Disable the pivot check and fetch the next batch of headers
|
|
||||||
pivoting = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// If the skeleton's finished, pull any remaining head headers directly from the origin
|
|
||||||
if skeleton && len(headers) == 0 {
|
|
||||||
// A malicious node might withhold advertised headers indefinitely
|
|
||||||
if from+uint64(MaxHeaderFetch)-1 <= head {
|
|
||||||
p.log.Warn("Peer withheld skeleton headers", "advertised", head, "withheld", from+uint64(MaxHeaderFetch)-1)
|
|
||||||
return fmt.Errorf("%w: withheld skeleton headers: advertised %d, withheld #%d", errStallingPeer, head, from+uint64(MaxHeaderFetch)-1)
|
|
||||||
}
|
|
||||||
p.log.Debug("No skeleton, fetching headers directly")
|
|
||||||
skeleton = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// If no more headers are inbound, notify the content fetchers and return
|
|
||||||
if len(headers) == 0 {
|
|
||||||
// Don't abort header fetches while the pivot is downloading
|
|
||||||
if !d.committed.Load() && pivot <= from {
|
|
||||||
p.log.Debug("No headers, waiting for pivot commit")
|
|
||||||
select {
|
|
||||||
case <-time.After(fsHeaderContCheck):
|
|
||||||
continue
|
|
||||||
case <-d.cancelCh:
|
|
||||||
return errCanceled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Pivot done (or not in snap sync) and no more headers, terminate the process
|
|
||||||
p.log.Debug("No more headers available")
|
|
||||||
select {
|
|
||||||
case d.headerProcCh <- nil:
|
|
||||||
return nil
|
|
||||||
case <-d.cancelCh:
|
|
||||||
return errCanceled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If we received a skeleton batch, resolve internals concurrently
|
|
||||||
var progressed bool
|
|
||||||
if skeleton {
|
|
||||||
filled, hashset, proced, err := d.fillHeaderSkeleton(from, headers)
|
|
||||||
if err != nil {
|
|
||||||
p.log.Debug("Skeleton chain invalid", "err", err)
|
|
||||||
return fmt.Errorf("%w: %v", errInvalidChain, err)
|
|
||||||
}
|
|
||||||
headers = filled[proced:]
|
|
||||||
hashes = hashset[proced:]
|
|
||||||
|
|
||||||
progressed = proced > 0
|
|
||||||
from += uint64(proced)
|
|
||||||
} else {
|
|
||||||
// A malicious node might withhold advertised headers indefinitely
|
|
||||||
if n := len(headers); n < MaxHeaderFetch && headers[n-1].Number.Uint64() < head {
|
|
||||||
p.log.Warn("Peer withheld headers", "advertised", head, "delivered", headers[n-1].Number.Uint64())
|
|
||||||
return fmt.Errorf("%w: withheld headers: advertised %d, delivered %d", errStallingPeer, head, headers[n-1].Number.Uint64())
|
|
||||||
}
|
|
||||||
// If we're closing in on the chain head, but haven't yet reached it, delay
|
|
||||||
// the last few headers so mini reorgs on the head don't cause invalid hash
|
|
||||||
// chain errors.
|
|
||||||
if n := len(headers); n > 0 {
|
|
||||||
// Retrieve the current head we're at
|
|
||||||
var head uint64
|
|
||||||
if mode == LightSync {
|
|
||||||
head = d.lightchain.CurrentHeader().Number.Uint64()
|
|
||||||
} else {
|
|
||||||
head = d.blockchain.CurrentSnapBlock().Number.Uint64()
|
|
||||||
if full := d.blockchain.CurrentBlock().Number.Uint64(); head < full {
|
|
||||||
head = full
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the head is below the common ancestor, we're actually deduplicating
|
|
||||||
// already existing chain segments, so use the ancestor as the fake head.
|
|
||||||
// Otherwise, we might end up delaying header deliveries pointlessly.
|
|
||||||
if head < ancestor {
|
|
||||||
head = ancestor
|
|
||||||
}
|
|
||||||
// If the head is way older than this batch, delay the last few headers
|
|
||||||
if head+uint64(reorgProtThreshold) < headers[n-1].Number.Uint64() {
|
|
||||||
delay := reorgProtHeaderDelay
|
|
||||||
if delay > n {
|
|
||||||
delay = n
|
|
||||||
}
|
|
||||||
headers = headers[:n-delay]
|
|
||||||
hashes = hashes[:n-delay]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If no headers have been delivered, or all of them have been delayed,
|
|
||||||
// sleep a bit and retry. Take care with headers already consumed during
|
|
||||||
// skeleton filling
|
|
||||||
if len(headers) == 0 && !progressed {
|
|
||||||
p.log.Trace("All headers delayed, waiting")
|
|
||||||
select {
|
|
||||||
case <-time.After(fsHeaderContCheck):
|
|
||||||
continue
|
|
||||||
case <-d.cancelCh:
|
|
||||||
return errCanceled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Insert any remaining new headers and fetch the next batch
|
|
||||||
if len(headers) > 0 {
|
|
||||||
p.log.Trace("Scheduling new headers", "count", len(headers), "from", from)
|
|
||||||
select {
|
|
||||||
case d.headerProcCh <- &headerTask{
|
|
||||||
headers: headers,
|
|
||||||
hashes: hashes,
|
|
||||||
}:
|
|
||||||
case <-d.cancelCh:
|
|
||||||
return errCanceled
|
|
||||||
}
|
|
||||||
from += uint64(len(headers))
|
|
||||||
}
|
|
||||||
// If we're still skeleton filling snap sync, check pivot staleness
|
|
||||||
// before continuing to the next skeleton filling
|
|
||||||
if skeleton && pivot > 0 {
|
|
||||||
pivoting = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fillHeaderSkeleton concurrently retrieves headers from all our available peers
|
|
||||||
// and maps them to the provided skeleton header chain.
|
|
||||||
//
|
|
||||||
// Any partial results from the beginning of the skeleton is (if possible) forwarded
|
|
||||||
// immediately to the header processor to keep the rest of the pipeline full even
|
|
||||||
// in the case of header stalls.
|
|
||||||
//
|
|
||||||
// The method returns the entire filled skeleton and also the number of headers
|
|
||||||
// already forwarded for processing.
|
|
||||||
func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) ([]*types.Header, []common.Hash, int, error) {
|
|
||||||
log.Debug("Filling up skeleton", "from", from)
|
|
||||||
d.queue.ScheduleSkeleton(from, skeleton)
|
|
||||||
|
|
||||||
err := d.concurrentFetch((*headerQueue)(d), false)
|
|
||||||
if err != nil {
|
|
||||||
log.Debug("Skeleton fill failed", "err", err)
|
|
||||||
}
|
|
||||||
filled, hashes, proced := d.queue.RetrieveHeaders()
|
|
||||||
if err == nil {
|
|
||||||
log.Debug("Skeleton fill succeeded", "filled", len(filled), "processed", proced)
|
|
||||||
}
|
|
||||||
return filled, hashes, proced, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetchBodies iteratively downloads the scheduled block bodies, taking any
|
// fetchBodies iteratively downloads the scheduled block bodies, taking any
|
||||||
// available peers, reserving a chunk of blocks for each, waiting for delivery
|
// available peers, reserving a chunk of blocks for each, waiting for delivery
|
||||||
// and also periodically checking for timeouts.
|
// and also periodically checking for timeouts.
|
||||||
func (d *Downloader) fetchBodies(from uint64, beaconMode bool) error {
|
func (d *Downloader) fetchBodies(from uint64) error {
|
||||||
log.Debug("Downloading block bodies", "origin", from)
|
log.Debug("Downloading block bodies", "origin", from)
|
||||||
err := d.concurrentFetch((*bodyQueue)(d), beaconMode)
|
err := d.concurrentFetch((*bodyQueue)(d))
|
||||||
|
|
||||||
log.Debug("Block body download terminated", "err", err)
|
log.Debug("Block body download terminated", "err", err)
|
||||||
return err
|
return err
|
||||||
|
|
@ -1261,9 +636,9 @@ func (d *Downloader) fetchBodies(from uint64, beaconMode bool) error {
|
||||||
// fetchReceipts iteratively downloads the scheduled block receipts, taking any
|
// fetchReceipts iteratively downloads the scheduled block receipts, taking any
|
||||||
// available peers, reserving a chunk of receipts for each, waiting for delivery
|
// available peers, reserving a chunk of receipts for each, waiting for delivery
|
||||||
// and also periodically checking for timeouts.
|
// and also periodically checking for timeouts.
|
||||||
func (d *Downloader) fetchReceipts(from uint64, beaconMode bool) error {
|
func (d *Downloader) fetchReceipts(from uint64) error {
|
||||||
log.Debug("Downloading receipts", "origin", from)
|
log.Debug("Downloading receipts", "origin", from)
|
||||||
err := d.concurrentFetch((*receiptQueue)(d), beaconMode)
|
err := d.concurrentFetch((*receiptQueue)(d))
|
||||||
|
|
||||||
log.Debug("Receipt download terminated", "err", err)
|
log.Debug("Receipt download terminated", "err", err)
|
||||||
return err
|
return err
|
||||||
|
|
@ -1272,10 +647,9 @@ func (d *Downloader) fetchReceipts(from uint64, beaconMode bool) error {
|
||||||
// processHeaders takes batches of retrieved headers from an input channel and
|
// processHeaders takes batches of retrieved headers from an input channel and
|
||||||
// keeps processing and scheduling them into the header chain and downloader's
|
// keeps processing and scheduling them into the header chain and downloader's
|
||||||
// queue until the stream ends or a failure occurs.
|
// queue until the stream ends or a failure occurs.
|
||||||
func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode bool) error {
|
func (d *Downloader) processHeaders(origin uint64) error {
|
||||||
var (
|
var (
|
||||||
mode = d.getMode()
|
mode = d.getMode()
|
||||||
gotHeaders = false // Wait for batches of headers to process
|
|
||||||
timer = time.NewTimer(time.Second)
|
timer = time.NewTimer(time.Second)
|
||||||
)
|
)
|
||||||
defer timer.Stop()
|
defer timer.Stop()
|
||||||
|
|
@ -1295,48 +669,11 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode
|
||||||
case <-d.cancelCh:
|
case <-d.cancelCh:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we're in legacy sync mode, we need to check total difficulty
|
|
||||||
// violations from malicious peers. That is not needed in beacon
|
|
||||||
// mode and we can skip to terminating sync.
|
|
||||||
if !beaconMode {
|
|
||||||
// If no headers were retrieved at all, the peer violated its TD promise that it had a
|
|
||||||
// better chain compared to ours. The only exception is if its promised blocks were
|
|
||||||
// already imported by other means (e.g. fetcher):
|
|
||||||
//
|
|
||||||
// R <remote peer>, L <local node>: Both at block 10
|
|
||||||
// R: Mine block 11, and propagate it to L
|
|
||||||
// L: Queue block 11 for import
|
|
||||||
// L: Notice that R's head and TD increased compared to ours, start sync
|
|
||||||
// L: Import of block 11 finishes
|
|
||||||
// L: Sync begins, and finds common ancestor at 11
|
|
||||||
// L: Request new headers up from 11 (R's TD was higher, it must have something)
|
|
||||||
// R: Nothing to give
|
|
||||||
if mode != LightSync {
|
|
||||||
head := d.blockchain.CurrentBlock()
|
|
||||||
if !gotHeaders && td.Cmp(d.blockchain.GetTd(head.Hash(), head.Number.Uint64())) > 0 {
|
|
||||||
return errStallingPeer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If snap or light syncing, ensure promised headers are indeed delivered. This is
|
|
||||||
// needed to detect scenarios where an attacker feeds a bad pivot and then bails out
|
|
||||||
// of delivering the post-pivot blocks that would flag the invalid content.
|
|
||||||
//
|
|
||||||
// This check cannot be executed "as is" for full imports, since blocks may still be
|
|
||||||
// queued for processing when the header download completes. However, as long as the
|
|
||||||
// peer gave us something useful, we're already happy/progressed (above check).
|
|
||||||
if mode == SnapSync || mode == LightSync {
|
|
||||||
head := d.lightchain.CurrentHeader()
|
|
||||||
if td.Cmp(d.lightchain.GetTd(head.Hash(), head.Number.Uint64())) > 0 {
|
|
||||||
return errStallingPeer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Otherwise split the chunk of headers into batches and process them
|
// Otherwise split the chunk of headers into batches and process them
|
||||||
headers, hashes := task.headers, task.hashes
|
headers, hashes := task.headers, task.hashes
|
||||||
|
|
||||||
gotHeaders = true
|
|
||||||
for len(headers) > 0 {
|
for len(headers) > 0 {
|
||||||
// Terminate if something failed in between processing chunks
|
// Terminate if something failed in between processing chunks
|
||||||
select {
|
select {
|
||||||
|
|
@ -1357,44 +694,12 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode
|
||||||
// Although the received headers might be all valid, a legacy
|
// Although the received headers might be all valid, a legacy
|
||||||
// PoW/PoA sync must not accept post-merge headers. Make sure
|
// PoW/PoA sync must not accept post-merge headers. Make sure
|
||||||
// that any transition is rejected at this point.
|
// that any transition is rejected at this point.
|
||||||
var (
|
|
||||||
rejected []*types.Header
|
|
||||||
td *big.Int
|
|
||||||
)
|
|
||||||
if !beaconMode && ttd != nil {
|
|
||||||
td = d.blockchain.GetTd(chunkHeaders[0].ParentHash, chunkHeaders[0].Number.Uint64()-1)
|
|
||||||
if td == nil {
|
|
||||||
// This should never really happen, but handle gracefully for now
|
|
||||||
log.Error("Failed to retrieve parent header TD", "number", chunkHeaders[0].Number.Uint64()-1, "hash", chunkHeaders[0].ParentHash)
|
|
||||||
return fmt.Errorf("%w: parent TD missing", errInvalidChain)
|
|
||||||
}
|
|
||||||
for i, header := range chunkHeaders {
|
|
||||||
td = new(big.Int).Add(td, header.Difficulty)
|
|
||||||
if td.Cmp(ttd) >= 0 {
|
|
||||||
// Terminal total difficulty reached, allow the last header in
|
|
||||||
if new(big.Int).Sub(td, header.Difficulty).Cmp(ttd) < 0 {
|
|
||||||
chunkHeaders, rejected = chunkHeaders[:i+1], chunkHeaders[i+1:]
|
|
||||||
if len(rejected) > 0 {
|
|
||||||
// Make a nicer user log as to the first TD truly rejected
|
|
||||||
td = new(big.Int).Add(td, rejected[0].Difficulty)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
chunkHeaders, rejected = chunkHeaders[:i], chunkHeaders[i:]
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(chunkHeaders) > 0 {
|
if len(chunkHeaders) > 0 {
|
||||||
if n, err := d.lightchain.InsertHeaderChain(chunkHeaders); err != nil {
|
if n, err := d.lightchain.InsertHeaderChain(chunkHeaders); err != nil {
|
||||||
log.Warn("Invalid header encountered", "number", chunkHeaders[n].Number, "hash", chunkHashes[n], "parent", chunkHeaders[n].ParentHash, "err", err)
|
log.Warn("Invalid header encountered", "number", chunkHeaders[n].Number, "hash", chunkHashes[n], "parent", chunkHeaders[n].ParentHash, "err", err)
|
||||||
return fmt.Errorf("%w: %v", errInvalidChain, err)
|
return fmt.Errorf("%w: %v", errInvalidChain, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(rejected) != 0 {
|
|
||||||
log.Info("Legacy sync reached merge threshold", "number", rejected[0].Number, "hash", rejected[0].Hash(), "td", td, "ttd", ttd)
|
|
||||||
return ErrMergeTransition
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Unless we're doing light chains, schedule the headers for associated content retrieval
|
// Unless we're doing light chains, schedule the headers for associated content retrieval
|
||||||
if mode == FullSync || mode == SnapSync {
|
if mode == FullSync || mode == SnapSync {
|
||||||
|
|
@ -1436,7 +741,7 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// processFullSyncContent takes fetch results from the queue and imports them into the chain.
|
// processFullSyncContent takes fetch results from the queue and imports them into the chain.
|
||||||
func (d *Downloader) processFullSyncContent(ttd *big.Int, beaconMode bool) error {
|
func (d *Downloader) processFullSyncContent() error {
|
||||||
for {
|
for {
|
||||||
results := d.queue.Results(true)
|
results := d.queue.Results(true)
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
|
|
@ -1445,44 +750,9 @@ func (d *Downloader) processFullSyncContent(ttd *big.Int, beaconMode bool) error
|
||||||
if d.chainInsertHook != nil {
|
if d.chainInsertHook != nil {
|
||||||
d.chainInsertHook(results)
|
d.chainInsertHook(results)
|
||||||
}
|
}
|
||||||
// Although the received blocks might be all valid, a legacy PoW/PoA sync
|
|
||||||
// must not accept post-merge blocks. Make sure that pre-merge blocks are
|
|
||||||
// imported, but post-merge ones are rejected.
|
|
||||||
var (
|
|
||||||
rejected []*fetchResult
|
|
||||||
td *big.Int
|
|
||||||
)
|
|
||||||
if !beaconMode && ttd != nil {
|
|
||||||
td = d.blockchain.GetTd(results[0].Header.ParentHash, results[0].Header.Number.Uint64()-1)
|
|
||||||
if td == nil {
|
|
||||||
// This should never really happen, but handle gracefully for now
|
|
||||||
log.Error("Failed to retrieve parent block TD", "number", results[0].Header.Number.Uint64()-1, "hash", results[0].Header.ParentHash)
|
|
||||||
return fmt.Errorf("%w: parent TD missing", errInvalidChain)
|
|
||||||
}
|
|
||||||
for i, result := range results {
|
|
||||||
td = new(big.Int).Add(td, result.Header.Difficulty)
|
|
||||||
if td.Cmp(ttd) >= 0 {
|
|
||||||
// Terminal total difficulty reached, allow the last block in
|
|
||||||
if new(big.Int).Sub(td, result.Header.Difficulty).Cmp(ttd) < 0 {
|
|
||||||
results, rejected = results[:i+1], results[i+1:]
|
|
||||||
if len(rejected) > 0 {
|
|
||||||
// Make a nicer user log as to the first TD truly rejected
|
|
||||||
td = new(big.Int).Add(td, rejected[0].Header.Difficulty)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
results, rejected = results[:i], results[i:]
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := d.importBlockResults(results); err != nil {
|
if err := d.importBlockResults(results); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(rejected) != 0 {
|
|
||||||
log.Info("Legacy sync reached merge threshold", "number", rejected[0].Header.Number, "hash", rejected[0].Header.Hash(), "td", td, "ttd", ttd)
|
|
||||||
return ErrMergeTransition
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1504,7 +774,7 @@ func (d *Downloader) importBlockResults(results []*fetchResult) error {
|
||||||
)
|
)
|
||||||
blocks := make([]*types.Block, len(results))
|
blocks := make([]*types.Block, len(results))
|
||||||
for i, result := range results {
|
for i, result := range results {
|
||||||
blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles).WithWithdrawals(result.Withdrawals)
|
blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.body())
|
||||||
}
|
}
|
||||||
// Downloaded blocks are always regarded as trusted after the
|
// Downloaded blocks are always regarded as trusted after the
|
||||||
// transition. Because the downloaded chain is guided by the
|
// transition. Because the downloaded chain is guided by the
|
||||||
|
|
@ -1726,7 +996,7 @@ func (d *Downloader) commitSnapSyncData(results []*fetchResult, stateSync *state
|
||||||
blocks := make([]*types.Block, len(results))
|
blocks := make([]*types.Block, len(results))
|
||||||
receipts := make([]types.Receipts, len(results))
|
receipts := make([]types.Receipts, len(results))
|
||||||
for i, result := range results {
|
for i, result := range results {
|
||||||
blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles).WithWithdrawals(result.Withdrawals)
|
blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.body())
|
||||||
receipts[i] = result.Receipts
|
receipts[i] = result.Receipts
|
||||||
}
|
}
|
||||||
if index, err := d.blockchain.InsertReceiptChain(blocks, receipts, d.ancientLimit); err != nil {
|
if index, err := d.blockchain.InsertReceiptChain(blocks, receipts, d.ancientLimit); err != nil {
|
||||||
|
|
@ -1737,7 +1007,7 @@ func (d *Downloader) commitSnapSyncData(results []*fetchResult, stateSync *state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) commitPivotBlock(result *fetchResult) error {
|
func (d *Downloader) commitPivotBlock(result *fetchResult) error {
|
||||||
block := types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles).WithWithdrawals(result.Withdrawals)
|
block := types.NewBlockWithHeader(result.Header).WithBody(result.body())
|
||||||
log.Debug("Committing snap sync pivot as new head", "number", block.Number(), "hash", block.Hash())
|
log.Debug("Committing snap sync pivot as new head", "number", block.Number(), "hash", block.Hash())
|
||||||
|
|
||||||
// Commit the pivot block as the new head, will require full sync from here on
|
// Commit the pivot block as the new head, will require full sync from here on
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,6 @@ package downloader
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -44,7 +42,6 @@ import (
|
||||||
|
|
||||||
// downloadTester is a test simulator for mocking out local block chain.
|
// downloadTester is a test simulator for mocking out local block chain.
|
||||||
type downloadTester struct {
|
type downloadTester struct {
|
||||||
freezer string
|
|
||||||
chain *core.BlockChain
|
chain *core.BlockChain
|
||||||
downloader *Downloader
|
downloader *Downloader
|
||||||
|
|
||||||
|
|
@ -59,8 +56,7 @@ func newTester(t *testing.T) *downloadTester {
|
||||||
|
|
||||||
// newTesterWithNotification creates a new downloader test mocker.
|
// newTesterWithNotification creates a new downloader test mocker.
|
||||||
func newTesterWithNotification(t *testing.T, success func()) *downloadTester {
|
func newTesterWithNotification(t *testing.T, success func()) *downloadTester {
|
||||||
freezer := t.TempDir()
|
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||||
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), freezer, "", false)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +73,6 @@ func newTesterWithNotification(t *testing.T, success func()) *downloadTester {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
tester := &downloadTester{
|
tester := &downloadTester{
|
||||||
freezer: freezer,
|
|
||||||
chain: chain,
|
chain: chain,
|
||||||
peers: make(map[string]*downloadTesterPeer),
|
peers: make(map[string]*downloadTesterPeer),
|
||||||
}
|
}
|
||||||
|
|
@ -90,27 +85,6 @@ func newTesterWithNotification(t *testing.T, success func()) *downloadTester {
|
||||||
func (dl *downloadTester) terminate() {
|
func (dl *downloadTester) terminate() {
|
||||||
dl.downloader.Terminate()
|
dl.downloader.Terminate()
|
||||||
dl.chain.Stop()
|
dl.chain.Stop()
|
||||||
|
|
||||||
os.RemoveAll(dl.freezer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sync starts synchronizing with a remote peer, blocking until it completes.
|
|
||||||
func (dl *downloadTester) sync(id string, td *big.Int, mode SyncMode) error {
|
|
||||||
head := dl.peers[id].chain.CurrentBlock()
|
|
||||||
if td == nil {
|
|
||||||
// If no particular TD was requested, load from the peer's blockchain
|
|
||||||
td = dl.peers[id].chain.GetTd(head.Hash(), head.Number.Uint64())
|
|
||||||
}
|
|
||||||
// Synchronise with the chosen peer and ensure proper cleanup afterwards
|
|
||||||
err := dl.downloader.synchronise(id, head.Hash(), td, nil, mode, false, nil)
|
|
||||||
select {
|
|
||||||
case <-dl.downloader.cancelCh:
|
|
||||||
// Ok, downloader fully cancelled after sync cycle
|
|
||||||
default:
|
|
||||||
// Downloader is still accepting packets, can block a peer up
|
|
||||||
panic("downloader active post sync cycle") // panic will be caught by tester
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newPeer registers a new block download source into the downloader.
|
// newPeer registers a new block download source into the downloader.
|
||||||
|
|
@ -122,7 +96,7 @@ func (dl *downloadTester) newPeer(id string, version uint, blocks []*types.Block
|
||||||
dl: dl,
|
dl: dl,
|
||||||
id: id,
|
id: id,
|
||||||
chain: newTestBlockchain(blocks),
|
chain: newTestBlockchain(blocks),
|
||||||
withholdHeaders: make(map[common.Hash]struct{}),
|
withholdBodies: make(map[common.Hash]struct{}),
|
||||||
}
|
}
|
||||||
dl.peers[id] = peer
|
dl.peers[id] = peer
|
||||||
|
|
||||||
|
|
@ -147,10 +121,9 @@ func (dl *downloadTester) dropPeer(id string) {
|
||||||
|
|
||||||
type downloadTesterPeer struct {
|
type downloadTesterPeer struct {
|
||||||
dl *downloadTester
|
dl *downloadTester
|
||||||
|
withholdBodies map[common.Hash]struct{}
|
||||||
id string
|
id string
|
||||||
chain *core.BlockChain
|
chain *core.BlockChain
|
||||||
|
|
||||||
withholdHeaders map[common.Hash]struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Head constructs a function to retrieve a peer's current head hash
|
// Head constructs a function to retrieve a peer's current head hash
|
||||||
|
|
@ -186,15 +159,6 @@ func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount i
|
||||||
Reverse: reverse,
|
Reverse: reverse,
|
||||||
}, nil)
|
}, nil)
|
||||||
headers := unmarshalRlpHeaders(rlpHeaders)
|
headers := unmarshalRlpHeaders(rlpHeaders)
|
||||||
// If a malicious peer is simulated withholding headers, delete them
|
|
||||||
for hash := range dlp.withholdHeaders {
|
|
||||||
for i, header := range headers {
|
|
||||||
if header.Hash() == hash {
|
|
||||||
headers = append(headers[:i], headers[i+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hashes := make([]common.Hash, len(headers))
|
hashes := make([]common.Hash, len(headers))
|
||||||
for i, header := range headers {
|
for i, header := range headers {
|
||||||
hashes[i] = header.Hash()
|
hashes[i] = header.Hash()
|
||||||
|
|
@ -230,15 +194,6 @@ func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int,
|
||||||
Reverse: reverse,
|
Reverse: reverse,
|
||||||
}, nil)
|
}, nil)
|
||||||
headers := unmarshalRlpHeaders(rlpHeaders)
|
headers := unmarshalRlpHeaders(rlpHeaders)
|
||||||
// If a malicious peer is simulated withholding headers, delete them
|
|
||||||
for hash := range dlp.withholdHeaders {
|
|
||||||
for i, header := range headers {
|
|
||||||
if header.Hash() == hash {
|
|
||||||
headers = append(headers[:i], headers[i+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hashes := make([]common.Hash, len(headers))
|
hashes := make([]common.Hash, len(headers))
|
||||||
for i, header := range headers {
|
for i, header := range headers {
|
||||||
hashes[i] = header.Hash()
|
hashes[i] = header.Hash()
|
||||||
|
|
@ -278,7 +233,13 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
|
||||||
)
|
)
|
||||||
hasher := trie.NewStackTrie(nil)
|
hasher := trie.NewStackTrie(nil)
|
||||||
for i, body := range bodies {
|
for i, body := range bodies {
|
||||||
txsHashes[i] = types.DeriveSha(types.Transactions(body.Transactions), hasher)
|
hash := types.DeriveSha(types.Transactions(body.Transactions), hasher)
|
||||||
|
if _, ok := dlp.withholdBodies[hash]; ok {
|
||||||
|
txsHashes = append(txsHashes[:i], txsHashes[i+1:]...)
|
||||||
|
uncleHashes = append(uncleHashes[:i], uncleHashes[i+1:]...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
txsHashes[i] = hash
|
||||||
uncleHashes[i] = types.CalcUncleHash(body.Uncles)
|
uncleHashes[i] = types.CalcUncleHash(body.Uncles)
|
||||||
}
|
}
|
||||||
req := ð.Request{
|
req := ð.Request{
|
||||||
|
|
@ -442,7 +403,10 @@ func TestCanonicalSynchronisation68Snap(t *testing.T) { testCanonSync(t, eth.ET
|
||||||
func TestCanonicalSynchronisation68Light(t *testing.T) { testCanonSync(t, eth.ETH68, LightSync) }
|
func TestCanonicalSynchronisation68Light(t *testing.T) { testCanonSync(t, eth.ETH68, LightSync) }
|
||||||
|
|
||||||
func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
|
func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
success := make(chan struct{})
|
||||||
|
tester := newTesterWithNotification(t, func() {
|
||||||
|
close(success)
|
||||||
|
})
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
// Create a small enough block chain to download
|
// Create a small enough block chain to download
|
||||||
|
|
@ -450,10 +414,15 @@ func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester.newPeer("peer", protocol, chain.blocks[1:])
|
tester.newPeer("peer", protocol, chain.blocks[1:])
|
||||||
|
|
||||||
// Synchronise with the peer and make sure all relevant data was retrieved
|
// Synchronise with the peer and make sure all relevant data was retrieved
|
||||||
if err := tester.sync("peer", nil, mode); err != nil {
|
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
|
||||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
t.Fatalf("failed to beacon-sync chain: %v", err)
|
||||||
}
|
}
|
||||||
|
select {
|
||||||
|
case <-success:
|
||||||
assertOwnChain(t, tester, len(chain.blocks))
|
assertOwnChain(t, tester, len(chain.blocks))
|
||||||
|
case <-time.NewTimer(time.Second * 3).C:
|
||||||
|
t.Fatalf("Failed to sync chain in three seconds")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that if a large batch of blocks are being downloaded, it is throttled
|
// Tests that if a large batch of blocks are being downloaded, it is throttled
|
||||||
|
|
@ -479,7 +448,7 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
// Start a synchronisation concurrently
|
// Start a synchronisation concurrently
|
||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
errc <- tester.sync("peer", nil, mode)
|
errc <- tester.downloader.BeaconSync(mode, testChainBase.blocks[len(testChainBase.blocks)-1].Header(), nil)
|
||||||
}()
|
}()
|
||||||
// Iteratively take some blocks, always checking the retrieval count
|
// Iteratively take some blocks, always checking the retrieval count
|
||||||
for {
|
for {
|
||||||
|
|
@ -535,132 +504,17 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that simple synchronization against a forked chain works correctly. In
|
|
||||||
// this test common ancestor lookup should *not* be short circuited, and a full
|
|
||||||
// binary search should be executed.
|
|
||||||
func TestForkedSync68Full(t *testing.T) { testForkedSync(t, eth.ETH68, FullSync) }
|
|
||||||
func TestForkedSync68Snap(t *testing.T) { testForkedSync(t, eth.ETH68, SnapSync) }
|
|
||||||
func TestForkedSync68Light(t *testing.T) { testForkedSync(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
|
||||||
tester := newTester(t)
|
|
||||||
defer tester.terminate()
|
|
||||||
|
|
||||||
chainA := testChainForkLightA.shorten(len(testChainBase.blocks) + 80)
|
|
||||||
chainB := testChainForkLightB.shorten(len(testChainBase.blocks) + 81)
|
|
||||||
tester.newPeer("fork A", protocol, chainA.blocks[1:])
|
|
||||||
tester.newPeer("fork B", protocol, chainB.blocks[1:])
|
|
||||||
// Synchronise with the peer and make sure all blocks were retrieved
|
|
||||||
if err := tester.sync("fork A", nil, mode); err != nil {
|
|
||||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
|
||||||
}
|
|
||||||
assertOwnChain(t, tester, len(chainA.blocks))
|
|
||||||
|
|
||||||
// Synchronise with the second peer and make sure that fork is pulled too
|
|
||||||
if err := tester.sync("fork B", nil, mode); err != nil {
|
|
||||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
|
||||||
}
|
|
||||||
assertOwnChain(t, tester, len(chainB.blocks))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that synchronising against a much shorter but much heavier fork works
|
|
||||||
// currently and is not dropped.
|
|
||||||
func TestHeavyForkedSync68Full(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, FullSync) }
|
|
||||||
func TestHeavyForkedSync68Snap(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, SnapSync) }
|
|
||||||
func TestHeavyForkedSync68Light(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
|
||||||
tester := newTester(t)
|
|
||||||
defer tester.terminate()
|
|
||||||
|
|
||||||
chainA := testChainForkLightA.shorten(len(testChainBase.blocks) + 80)
|
|
||||||
chainB := testChainForkHeavy.shorten(len(testChainBase.blocks) + 79)
|
|
||||||
tester.newPeer("light", protocol, chainA.blocks[1:])
|
|
||||||
tester.newPeer("heavy", protocol, chainB.blocks[1:])
|
|
||||||
|
|
||||||
// Synchronise with the peer and make sure all blocks were retrieved
|
|
||||||
if err := tester.sync("light", nil, mode); err != nil {
|
|
||||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
|
||||||
}
|
|
||||||
assertOwnChain(t, tester, len(chainA.blocks))
|
|
||||||
|
|
||||||
// Synchronise with the second peer and make sure that fork is pulled too
|
|
||||||
if err := tester.sync("heavy", nil, mode); err != nil {
|
|
||||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
|
||||||
}
|
|
||||||
assertOwnChain(t, tester, len(chainB.blocks))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that chain forks are contained within a certain interval of the current
|
|
||||||
// chain head, ensuring that malicious peers cannot waste resources by feeding
|
|
||||||
// long dead chains.
|
|
||||||
func TestBoundedForkedSync68Full(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, FullSync) }
|
|
||||||
func TestBoundedForkedSync68Snap(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, SnapSync) }
|
|
||||||
func TestBoundedForkedSync68Light(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
|
||||||
tester := newTester(t)
|
|
||||||
defer tester.terminate()
|
|
||||||
|
|
||||||
chainA := testChainForkLightA
|
|
||||||
chainB := testChainForkLightB
|
|
||||||
tester.newPeer("original", protocol, chainA.blocks[1:])
|
|
||||||
tester.newPeer("rewriter", protocol, chainB.blocks[1:])
|
|
||||||
|
|
||||||
// Synchronise with the peer and make sure all blocks were retrieved
|
|
||||||
if err := tester.sync("original", nil, mode); err != nil {
|
|
||||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
|
||||||
}
|
|
||||||
assertOwnChain(t, tester, len(chainA.blocks))
|
|
||||||
|
|
||||||
// Synchronise with the second peer and ensure that the fork is rejected to being too old
|
|
||||||
if err := tester.sync("rewriter", nil, mode); err != errInvalidAncestor {
|
|
||||||
t.Fatalf("sync failure mismatch: have %v, want %v", err, errInvalidAncestor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that chain forks are contained within a certain interval of the current
|
|
||||||
// chain head for short but heavy forks too. These are a bit special because they
|
|
||||||
// take different ancestor lookup paths.
|
|
||||||
func TestBoundedHeavyForkedSync68Full(t *testing.T) {
|
|
||||||
testBoundedHeavyForkedSync(t, eth.ETH68, FullSync)
|
|
||||||
}
|
|
||||||
func TestBoundedHeavyForkedSync68Snap(t *testing.T) {
|
|
||||||
testBoundedHeavyForkedSync(t, eth.ETH68, SnapSync)
|
|
||||||
}
|
|
||||||
func TestBoundedHeavyForkedSync68Light(t *testing.T) {
|
|
||||||
testBoundedHeavyForkedSync(t, eth.ETH68, LightSync)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) {
|
|
||||||
tester := newTester(t)
|
|
||||||
defer tester.terminate()
|
|
||||||
|
|
||||||
// Create a long enough forked chain
|
|
||||||
chainA := testChainForkLightA
|
|
||||||
chainB := testChainForkHeavy
|
|
||||||
tester.newPeer("original", protocol, chainA.blocks[1:])
|
|
||||||
|
|
||||||
// Synchronise with the peer and make sure all blocks were retrieved
|
|
||||||
if err := tester.sync("original", nil, mode); err != nil {
|
|
||||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
|
||||||
}
|
|
||||||
assertOwnChain(t, tester, len(chainA.blocks))
|
|
||||||
|
|
||||||
tester.newPeer("heavy-rewriter", protocol, chainB.blocks[1:])
|
|
||||||
// Synchronise with the second peer and ensure that the fork is rejected to being too old
|
|
||||||
if err := tester.sync("heavy-rewriter", nil, mode); err != errInvalidAncestor {
|
|
||||||
t.Fatalf("sync failure mismatch: have %v, want %v", err, errInvalidAncestor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that a canceled download wipes all previously accumulated state.
|
// Tests that a canceled download wipes all previously accumulated state.
|
||||||
func TestCancel68Full(t *testing.T) { testCancel(t, eth.ETH68, FullSync) }
|
func TestCancel68Full(t *testing.T) { testCancel(t, eth.ETH68, FullSync) }
|
||||||
func TestCancel68Snap(t *testing.T) { testCancel(t, eth.ETH68, SnapSync) }
|
func TestCancel68Snap(t *testing.T) { testCancel(t, eth.ETH68, SnapSync) }
|
||||||
func TestCancel68Light(t *testing.T) { testCancel(t, eth.ETH68, LightSync) }
|
func TestCancel68Light(t *testing.T) { testCancel(t, eth.ETH68, LightSync) }
|
||||||
|
|
||||||
func testCancel(t *testing.T, protocol uint, mode SyncMode) {
|
func testCancel(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
complete := make(chan struct{})
|
||||||
|
success := func() {
|
||||||
|
close(complete)
|
||||||
|
}
|
||||||
|
tester := newTesterWithNotification(t, success)
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
chain := testChainBase.shorten(MaxHeaderFetch)
|
chain := testChainBase.shorten(MaxHeaderFetch)
|
||||||
|
|
@ -672,38 +526,16 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
t.Errorf("download queue not idle")
|
t.Errorf("download queue not idle")
|
||||||
}
|
}
|
||||||
// Synchronise with the peer, but cancel afterwards
|
// Synchronise with the peer, but cancel afterwards
|
||||||
if err := tester.sync("peer", nil, mode); err != nil {
|
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
|
||||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
t.Fatalf("failed to synchronise blocks: %v", err)
|
||||||
}
|
}
|
||||||
|
<-complete
|
||||||
tester.downloader.Cancel()
|
tester.downloader.Cancel()
|
||||||
if !tester.downloader.queue.Idle() {
|
if !tester.downloader.queue.Idle() {
|
||||||
t.Errorf("download queue not idle")
|
t.Errorf("download queue not idle")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that synchronisation from multiple peers works as intended (multi thread sanity test).
|
|
||||||
func TestMultiSynchronisation68Full(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, FullSync) }
|
|
||||||
func TestMultiSynchronisation68Snap(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, SnapSync) }
|
|
||||||
func TestMultiSynchronisation68Light(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) {
|
|
||||||
tester := newTester(t)
|
|
||||||
defer tester.terminate()
|
|
||||||
|
|
||||||
// Create various peers with various parts of the chain
|
|
||||||
targetPeers := 8
|
|
||||||
chain := testChainBase.shorten(targetPeers * 100)
|
|
||||||
|
|
||||||
for i := 0; i < targetPeers; i++ {
|
|
||||||
id := fmt.Sprintf("peer #%d", i)
|
|
||||||
tester.newPeer(id, protocol, chain.shorten(len(chain.blocks) / (i + 1)).blocks[1:])
|
|
||||||
}
|
|
||||||
if err := tester.sync("peer #0", nil, mode); err != nil {
|
|
||||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
|
||||||
}
|
|
||||||
assertOwnChain(t, tester, len(chain.blocks))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that synchronisations behave well in multi-version protocol environments
|
// Tests that synchronisations behave well in multi-version protocol environments
|
||||||
// and not wreak havoc on other nodes in the network.
|
// and not wreak havoc on other nodes in the network.
|
||||||
func TestMultiProtoSynchronisation68Full(t *testing.T) { testMultiProtoSync(t, eth.ETH68, FullSync) }
|
func TestMultiProtoSynchronisation68Full(t *testing.T) { testMultiProtoSync(t, eth.ETH68, FullSync) }
|
||||||
|
|
@ -711,7 +543,11 @@ func TestMultiProtoSynchronisation68Snap(t *testing.T) { testMultiProtoSync(t,
|
||||||
func TestMultiProtoSynchronisation68Light(t *testing.T) { testMultiProtoSync(t, eth.ETH68, LightSync) }
|
func TestMultiProtoSynchronisation68Light(t *testing.T) { testMultiProtoSync(t, eth.ETH68, LightSync) }
|
||||||
|
|
||||||
func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
|
func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
complete := make(chan struct{})
|
||||||
|
success := func() {
|
||||||
|
close(complete)
|
||||||
|
}
|
||||||
|
tester := newTesterWithNotification(t, success)
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
// Create a small enough block chain to download
|
// Create a small enough block chain to download
|
||||||
|
|
@ -720,9 +556,14 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
// Create peers of every type
|
// Create peers of every type
|
||||||
tester.newPeer("peer 68", eth.ETH68, chain.blocks[1:])
|
tester.newPeer("peer 68", eth.ETH68, chain.blocks[1:])
|
||||||
|
|
||||||
// Synchronise with the requested peer and make sure all blocks were retrieved
|
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
|
||||||
if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil {
|
t.Fatalf("failed to start beacon sync: #{err}")
|
||||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
}
|
||||||
|
select {
|
||||||
|
case <-complete:
|
||||||
|
break
|
||||||
|
case <-time.NewTimer(time.Second * 3).C:
|
||||||
|
t.Fatalf("Failed to sync chain in three seconds")
|
||||||
}
|
}
|
||||||
assertOwnChain(t, tester, len(chain.blocks))
|
assertOwnChain(t, tester, len(chain.blocks))
|
||||||
|
|
||||||
|
|
@ -742,7 +583,10 @@ func TestEmptyShortCircuit68Snap(t *testing.T) { testEmptyShortCircuit(t, eth.E
|
||||||
func TestEmptyShortCircuit68Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, LightSync) }
|
func TestEmptyShortCircuit68Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, LightSync) }
|
||||||
|
|
||||||
func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
|
func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester := newTester(t)
|
success := make(chan struct{})
|
||||||
|
tester := newTesterWithNotification(t, func() {
|
||||||
|
close(success)
|
||||||
|
})
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
// Create a block chain to download
|
// Create a block chain to download
|
||||||
|
|
@ -757,10 +601,19 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
tester.downloader.receiptFetchHook = func(headers []*types.Header) {
|
tester.downloader.receiptFetchHook = func(headers []*types.Header) {
|
||||||
receiptsHave.Add(int32(len(headers)))
|
receiptsHave.Add(int32(len(headers)))
|
||||||
}
|
}
|
||||||
// Synchronise with the peer and make sure all blocks were retrieved
|
|
||||||
if err := tester.sync("peer", nil, mode); err != nil {
|
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
|
||||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
t.Fatalf("failed to synchronise blocks: %v", err)
|
||||||
}
|
}
|
||||||
|
select {
|
||||||
|
case <-success:
|
||||||
|
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
|
||||||
|
HighestBlock: uint64(len(chain.blocks) - 1),
|
||||||
|
CurrentBlock: uint64(len(chain.blocks) - 1),
|
||||||
|
})
|
||||||
|
case <-time.NewTimer(time.Second * 3).C:
|
||||||
|
t.Fatalf("Failed to sync chain in three seconds")
|
||||||
|
}
|
||||||
assertOwnChain(t, tester, len(chain.blocks))
|
assertOwnChain(t, tester, len(chain.blocks))
|
||||||
|
|
||||||
// Validate the number of block bodies that should have been requested
|
// Validate the number of block bodies that should have been requested
|
||||||
|
|
@ -783,195 +636,6 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that headers are enqueued continuously, preventing malicious nodes from
|
|
||||||
// stalling the downloader by feeding gapped header chains.
|
|
||||||
func TestMissingHeaderAttack68Full(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, FullSync) }
|
|
||||||
func TestMissingHeaderAttack68Snap(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, SnapSync) }
|
|
||||||
func TestMissingHeaderAttack68Light(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) {
|
|
||||||
tester := newTester(t)
|
|
||||||
defer tester.terminate()
|
|
||||||
|
|
||||||
chain := testChainBase.shorten(blockCacheMaxItems - 15)
|
|
||||||
|
|
||||||
attacker := tester.newPeer("attack", protocol, chain.blocks[1:])
|
|
||||||
attacker.withholdHeaders[chain.blocks[len(chain.blocks)/2-1].Hash()] = struct{}{}
|
|
||||||
|
|
||||||
if err := tester.sync("attack", nil, mode); err == nil {
|
|
||||||
t.Fatalf("succeeded attacker synchronisation")
|
|
||||||
}
|
|
||||||
// Synchronise with the valid peer and make sure sync succeeds
|
|
||||||
tester.newPeer("valid", protocol, chain.blocks[1:])
|
|
||||||
if err := tester.sync("valid", nil, mode); err != nil {
|
|
||||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
|
||||||
}
|
|
||||||
assertOwnChain(t, tester, len(chain.blocks))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that if requested headers are shifted (i.e. first is missing), the queue
|
|
||||||
// detects the invalid numbering.
|
|
||||||
func TestShiftedHeaderAttack68Full(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, FullSync) }
|
|
||||||
func TestShiftedHeaderAttack68Snap(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, SnapSync) }
|
|
||||||
func TestShiftedHeaderAttack68Light(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) {
|
|
||||||
tester := newTester(t)
|
|
||||||
defer tester.terminate()
|
|
||||||
|
|
||||||
chain := testChainBase.shorten(blockCacheMaxItems - 15)
|
|
||||||
|
|
||||||
// Attempt a full sync with an attacker feeding shifted headers
|
|
||||||
attacker := tester.newPeer("attack", protocol, chain.blocks[1:])
|
|
||||||
attacker.withholdHeaders[chain.blocks[1].Hash()] = struct{}{}
|
|
||||||
|
|
||||||
if err := tester.sync("attack", nil, mode); err == nil {
|
|
||||||
t.Fatalf("succeeded attacker synchronisation")
|
|
||||||
}
|
|
||||||
// Synchronise with the valid peer and make sure sync succeeds
|
|
||||||
tester.newPeer("valid", protocol, chain.blocks[1:])
|
|
||||||
if err := tester.sync("valid", nil, mode); err != nil {
|
|
||||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
|
||||||
}
|
|
||||||
assertOwnChain(t, tester, len(chain.blocks))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that a peer advertising a high TD doesn't get to stall the downloader
|
|
||||||
// afterwards by not sending any useful hashes.
|
|
||||||
func TestHighTDStarvationAttack68Full(t *testing.T) {
|
|
||||||
testHighTDStarvationAttack(t, eth.ETH68, FullSync)
|
|
||||||
}
|
|
||||||
func TestHighTDStarvationAttack68Snap(t *testing.T) {
|
|
||||||
testHighTDStarvationAttack(t, eth.ETH68, SnapSync)
|
|
||||||
}
|
|
||||||
func TestHighTDStarvationAttack68Light(t *testing.T) {
|
|
||||||
testHighTDStarvationAttack(t, eth.ETH68, LightSync)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) {
|
|
||||||
tester := newTester(t)
|
|
||||||
defer tester.terminate()
|
|
||||||
|
|
||||||
chain := testChainBase.shorten(1)
|
|
||||||
tester.newPeer("attack", protocol, chain.blocks[1:])
|
|
||||||
if err := tester.sync("attack", big.NewInt(1000000), mode); err != errStallingPeer {
|
|
||||||
t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errStallingPeer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that misbehaving peers are disconnected, whilst behaving ones are not.
|
|
||||||
func TestBlockHeaderAttackerDropping68(t *testing.T) { testBlockHeaderAttackerDropping(t, eth.ETH68) }
|
|
||||||
|
|
||||||
func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) {
|
|
||||||
// Define the disconnection requirement for individual hash fetch errors
|
|
||||||
tests := []struct {
|
|
||||||
result error
|
|
||||||
drop bool
|
|
||||||
}{
|
|
||||||
{nil, false}, // Sync succeeded, all is well
|
|
||||||
{errBusy, false}, // Sync is already in progress, no problem
|
|
||||||
{errUnknownPeer, false}, // Peer is unknown, was already dropped, don't double drop
|
|
||||||
{errBadPeer, true}, // Peer was deemed bad for some reason, drop it
|
|
||||||
{errStallingPeer, true}, // Peer was detected to be stalling, drop it
|
|
||||||
{errUnsyncedPeer, true}, // Peer was detected to be unsynced, drop it
|
|
||||||
{errNoPeers, false}, // No peers to download from, soft race, no issue
|
|
||||||
{errTimeout, true}, // No hashes received in due time, drop the peer
|
|
||||||
{errEmptyHeaderSet, true}, // No headers were returned as a response, drop as it's a dead end
|
|
||||||
{errPeersUnavailable, true}, // Nobody had the advertised blocks, drop the advertiser
|
|
||||||
{errInvalidAncestor, true}, // Agreed upon ancestor is not acceptable, drop the chain rewriter
|
|
||||||
{errInvalidChain, true}, // Hash chain was detected as invalid, definitely drop
|
|
||||||
{errInvalidBody, false}, // A bad peer was detected, but not the sync origin
|
|
||||||
{errInvalidReceipt, false}, // A bad peer was detected, but not the sync origin
|
|
||||||
{errCancelContentProcessing, false}, // Synchronisation was canceled, origin may be innocent, don't drop
|
|
||||||
}
|
|
||||||
// Run the tests and check disconnection status
|
|
||||||
tester := newTester(t)
|
|
||||||
defer tester.terminate()
|
|
||||||
chain := testChainBase.shorten(1)
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
// Register a new peer and ensure its presence
|
|
||||||
id := fmt.Sprintf("test %d", i)
|
|
||||||
tester.newPeer(id, protocol, chain.blocks[1:])
|
|
||||||
if _, ok := tester.peers[id]; !ok {
|
|
||||||
t.Fatalf("test %d: registered peer not found", i)
|
|
||||||
}
|
|
||||||
// Simulate a synchronisation and check the required result
|
|
||||||
tester.downloader.synchroniseMock = func(string, common.Hash) error { return tt.result }
|
|
||||||
|
|
||||||
tester.downloader.LegacySync(id, tester.chain.Genesis().Hash(), big.NewInt(1000), nil, FullSync)
|
|
||||||
if _, ok := tester.peers[id]; !ok != tt.drop {
|
|
||||||
t.Errorf("test %d: peer drop mismatch for %v: have %v, want %v", i, tt.result, !ok, tt.drop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that synchronisation progress (origin block number, current block number
|
|
||||||
// and highest block number) is tracked and updated correctly.
|
|
||||||
func TestSyncProgress68Full(t *testing.T) { testSyncProgress(t, eth.ETH68, FullSync) }
|
|
||||||
func TestSyncProgress68Snap(t *testing.T) { testSyncProgress(t, eth.ETH68, SnapSync) }
|
|
||||||
func TestSyncProgress68Light(t *testing.T) { testSyncProgress(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
|
||||||
tester := newTester(t)
|
|
||||||
defer tester.terminate()
|
|
||||||
|
|
||||||
chain := testChainBase.shorten(blockCacheMaxItems - 15)
|
|
||||||
|
|
||||||
// Set a sync init hook to catch progress changes
|
|
||||||
starting := make(chan struct{})
|
|
||||||
progress := make(chan struct{})
|
|
||||||
|
|
||||||
tester.downloader.syncInitHook = func(origin, latest uint64) {
|
|
||||||
starting <- struct{}{}
|
|
||||||
<-progress
|
|
||||||
}
|
|
||||||
checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{})
|
|
||||||
|
|
||||||
// Synchronise half the blocks and check initial progress
|
|
||||||
tester.newPeer("peer-half", protocol, chain.shorten(len(chain.blocks) / 2).blocks[1:])
|
|
||||||
pending := new(sync.WaitGroup)
|
|
||||||
pending.Add(1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer pending.Done()
|
|
||||||
if err := tester.sync("peer-half", nil, mode); err != nil {
|
|
||||||
panic(fmt.Sprintf("failed to synchronise blocks: %v", err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
<-starting
|
|
||||||
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
|
|
||||||
HighestBlock: uint64(len(chain.blocks)/2 - 1),
|
|
||||||
})
|
|
||||||
progress <- struct{}{}
|
|
||||||
pending.Wait()
|
|
||||||
|
|
||||||
// Synchronise all the blocks and check continuation progress
|
|
||||||
tester.newPeer("peer-full", protocol, chain.blocks[1:])
|
|
||||||
pending.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer pending.Done()
|
|
||||||
if err := tester.sync("peer-full", nil, mode); err != nil {
|
|
||||||
panic(fmt.Sprintf("failed to synchronise blocks: %v", err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
<-starting
|
|
||||||
checkProgress(t, tester.downloader, "completing", ethereum.SyncProgress{
|
|
||||||
StartingBlock: uint64(len(chain.blocks)/2 - 1),
|
|
||||||
CurrentBlock: uint64(len(chain.blocks)/2 - 1),
|
|
||||||
HighestBlock: uint64(len(chain.blocks) - 1),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Check final progress after successful sync
|
|
||||||
progress <- struct{}{}
|
|
||||||
pending.Wait()
|
|
||||||
checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{
|
|
||||||
StartingBlock: uint64(len(chain.blocks)/2 - 1),
|
|
||||||
CurrentBlock: uint64(len(chain.blocks) - 1),
|
|
||||||
HighestBlock: uint64(len(chain.blocks) - 1),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.SyncProgress) {
|
func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.SyncProgress) {
|
||||||
// Mark this method as a helper to report errors at callsite, not in here
|
// Mark this method as a helper to report errors at callsite, not in here
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
@ -982,296 +646,12 @@ func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.Sync
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that synchronisation progress (origin block number and highest block
|
|
||||||
// number) is tracked and updated correctly in case of a fork (or manual head
|
|
||||||
// revertal).
|
|
||||||
func TestForkedSyncProgress68Full(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, FullSync) }
|
|
||||||
func TestForkedSyncProgress68Snap(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, SnapSync) }
|
|
||||||
func TestForkedSyncProgress68Light(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
|
||||||
tester := newTester(t)
|
|
||||||
defer tester.terminate()
|
|
||||||
|
|
||||||
chainA := testChainForkLightA.shorten(len(testChainBase.blocks) + MaxHeaderFetch)
|
|
||||||
chainB := testChainForkLightB.shorten(len(testChainBase.blocks) + MaxHeaderFetch)
|
|
||||||
|
|
||||||
// Set a sync init hook to catch progress changes
|
|
||||||
starting := make(chan struct{})
|
|
||||||
progress := make(chan struct{})
|
|
||||||
|
|
||||||
tester.downloader.syncInitHook = func(origin, latest uint64) {
|
|
||||||
starting <- struct{}{}
|
|
||||||
<-progress
|
|
||||||
}
|
|
||||||
checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{})
|
|
||||||
|
|
||||||
// Synchronise with one of the forks and check progress
|
|
||||||
tester.newPeer("fork A", protocol, chainA.blocks[1:])
|
|
||||||
pending := new(sync.WaitGroup)
|
|
||||||
pending.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer pending.Done()
|
|
||||||
if err := tester.sync("fork A", nil, mode); err != nil {
|
|
||||||
panic(fmt.Sprintf("failed to synchronise blocks: %v", err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
<-starting
|
|
||||||
|
|
||||||
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
|
|
||||||
HighestBlock: uint64(len(chainA.blocks) - 1),
|
|
||||||
})
|
|
||||||
progress <- struct{}{}
|
|
||||||
pending.Wait()
|
|
||||||
|
|
||||||
// Simulate a successful sync above the fork
|
|
||||||
tester.downloader.syncStatsChainOrigin = tester.downloader.syncStatsChainHeight
|
|
||||||
|
|
||||||
// Synchronise with the second fork and check progress resets
|
|
||||||
tester.newPeer("fork B", protocol, chainB.blocks[1:])
|
|
||||||
pending.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer pending.Done()
|
|
||||||
if err := tester.sync("fork B", nil, mode); err != nil {
|
|
||||||
panic(fmt.Sprintf("failed to synchronise blocks: %v", err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
<-starting
|
|
||||||
checkProgress(t, tester.downloader, "forking", ethereum.SyncProgress{
|
|
||||||
StartingBlock: uint64(len(testChainBase.blocks)) - 1,
|
|
||||||
CurrentBlock: uint64(len(chainA.blocks) - 1),
|
|
||||||
HighestBlock: uint64(len(chainB.blocks) - 1),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Check final progress after successful sync
|
|
||||||
progress <- struct{}{}
|
|
||||||
pending.Wait()
|
|
||||||
checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{
|
|
||||||
StartingBlock: uint64(len(testChainBase.blocks)) - 1,
|
|
||||||
CurrentBlock: uint64(len(chainB.blocks) - 1),
|
|
||||||
HighestBlock: uint64(len(chainB.blocks) - 1),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that if synchronisation is aborted due to some failure, then the progress
|
|
||||||
// origin is not updated in the next sync cycle, as it should be considered the
|
|
||||||
// continuation of the previous sync and not a new instance.
|
|
||||||
func TestFailedSyncProgress68Full(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, FullSync) }
|
|
||||||
func TestFailedSyncProgress68Snap(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, SnapSync) }
|
|
||||||
func TestFailedSyncProgress68Light(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
|
||||||
tester := newTester(t)
|
|
||||||
defer tester.terminate()
|
|
||||||
|
|
||||||
chain := testChainBase.shorten(blockCacheMaxItems - 15)
|
|
||||||
|
|
||||||
// Set a sync init hook to catch progress changes
|
|
||||||
starting := make(chan struct{})
|
|
||||||
progress := make(chan struct{})
|
|
||||||
|
|
||||||
tester.downloader.syncInitHook = func(origin, latest uint64) {
|
|
||||||
starting <- struct{}{}
|
|
||||||
<-progress
|
|
||||||
}
|
|
||||||
checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{})
|
|
||||||
|
|
||||||
// Attempt a full sync with a faulty peer
|
|
||||||
missing := len(chain.blocks)/2 - 1
|
|
||||||
|
|
||||||
faulter := tester.newPeer("faulty", protocol, chain.blocks[1:])
|
|
||||||
faulter.withholdHeaders[chain.blocks[missing].Hash()] = struct{}{}
|
|
||||||
|
|
||||||
pending := new(sync.WaitGroup)
|
|
||||||
pending.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer pending.Done()
|
|
||||||
if err := tester.sync("faulty", nil, mode); err == nil {
|
|
||||||
panic("succeeded faulty synchronisation")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
<-starting
|
|
||||||
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
|
|
||||||
HighestBlock: uint64(len(chain.blocks) - 1),
|
|
||||||
})
|
|
||||||
progress <- struct{}{}
|
|
||||||
pending.Wait()
|
|
||||||
afterFailedSync := tester.downloader.Progress()
|
|
||||||
|
|
||||||
// Synchronise with a good peer and check that the progress origin remind the same
|
|
||||||
// after a failure
|
|
||||||
tester.newPeer("valid", protocol, chain.blocks[1:])
|
|
||||||
pending.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer pending.Done()
|
|
||||||
if err := tester.sync("valid", nil, mode); err != nil {
|
|
||||||
panic(fmt.Sprintf("failed to synchronise blocks: %v", err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
<-starting
|
|
||||||
checkProgress(t, tester.downloader, "completing", afterFailedSync)
|
|
||||||
|
|
||||||
// Check final progress after successful sync
|
|
||||||
progress <- struct{}{}
|
|
||||||
pending.Wait()
|
|
||||||
checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{
|
|
||||||
CurrentBlock: uint64(len(chain.blocks) - 1),
|
|
||||||
HighestBlock: uint64(len(chain.blocks) - 1),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that if an attacker fakes a chain height, after the attack is detected,
|
|
||||||
// the progress height is successfully reduced at the next sync invocation.
|
|
||||||
func TestFakedSyncProgress68Full(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, FullSync) }
|
|
||||||
func TestFakedSyncProgress68Snap(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, SnapSync) }
|
|
||||||
func TestFakedSyncProgress68Light(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, LightSync) }
|
|
||||||
|
|
||||||
func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
|
||||||
tester := newTester(t)
|
|
||||||
defer tester.terminate()
|
|
||||||
|
|
||||||
chain := testChainBase.shorten(blockCacheMaxItems - 15)
|
|
||||||
|
|
||||||
// Set a sync init hook to catch progress changes
|
|
||||||
starting := make(chan struct{})
|
|
||||||
progress := make(chan struct{})
|
|
||||||
tester.downloader.syncInitHook = func(origin, latest uint64) {
|
|
||||||
starting <- struct{}{}
|
|
||||||
<-progress
|
|
||||||
}
|
|
||||||
checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{})
|
|
||||||
|
|
||||||
// Create and sync with an attacker that promises a higher chain than available.
|
|
||||||
attacker := tester.newPeer("attack", protocol, chain.blocks[1:])
|
|
||||||
numMissing := 5
|
|
||||||
for i := len(chain.blocks) - 2; i > len(chain.blocks)-numMissing; i-- {
|
|
||||||
attacker.withholdHeaders[chain.blocks[i].Hash()] = struct{}{}
|
|
||||||
}
|
|
||||||
pending := new(sync.WaitGroup)
|
|
||||||
pending.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer pending.Done()
|
|
||||||
if err := tester.sync("attack", nil, mode); err == nil {
|
|
||||||
panic("succeeded attacker synchronisation")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
<-starting
|
|
||||||
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
|
|
||||||
HighestBlock: uint64(len(chain.blocks) - 1),
|
|
||||||
})
|
|
||||||
progress <- struct{}{}
|
|
||||||
pending.Wait()
|
|
||||||
afterFailedSync := tester.downloader.Progress()
|
|
||||||
|
|
||||||
// Synchronise with a good peer and check that the progress height has been reduced to
|
|
||||||
// the true value.
|
|
||||||
validChain := chain.shorten(len(chain.blocks) - numMissing)
|
|
||||||
tester.newPeer("valid", protocol, validChain.blocks[1:])
|
|
||||||
pending.Add(1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer pending.Done()
|
|
||||||
if err := tester.sync("valid", nil, mode); err != nil {
|
|
||||||
panic(fmt.Sprintf("failed to synchronise blocks: %v", err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
<-starting
|
|
||||||
checkProgress(t, tester.downloader, "completing", ethereum.SyncProgress{
|
|
||||||
CurrentBlock: afterFailedSync.CurrentBlock,
|
|
||||||
HighestBlock: uint64(len(validChain.blocks) - 1),
|
|
||||||
})
|
|
||||||
// Check final progress after successful sync.
|
|
||||||
progress <- struct{}{}
|
|
||||||
pending.Wait()
|
|
||||||
checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{
|
|
||||||
CurrentBlock: uint64(len(validChain.blocks) - 1),
|
|
||||||
HighestBlock: uint64(len(validChain.blocks) - 1),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoteHeaderRequestSpan(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
remoteHeight uint64
|
|
||||||
localHeight uint64
|
|
||||||
expected []int
|
|
||||||
}{
|
|
||||||
// Remote is way higher. We should ask for the remote head and go backwards
|
|
||||||
{1500, 1000,
|
|
||||||
[]int{1323, 1339, 1355, 1371, 1387, 1403, 1419, 1435, 1451, 1467, 1483, 1499},
|
|
||||||
},
|
|
||||||
{15000, 13006,
|
|
||||||
[]int{14823, 14839, 14855, 14871, 14887, 14903, 14919, 14935, 14951, 14967, 14983, 14999},
|
|
||||||
},
|
|
||||||
// Remote is pretty close to us. We don't have to fetch as many
|
|
||||||
{1200, 1150,
|
|
||||||
[]int{1149, 1154, 1159, 1164, 1169, 1174, 1179, 1184, 1189, 1194, 1199},
|
|
||||||
},
|
|
||||||
// Remote is equal to us (so on a fork with higher td)
|
|
||||||
// We should get the closest couple of ancestors
|
|
||||||
{1500, 1500,
|
|
||||||
[]int{1497, 1499},
|
|
||||||
},
|
|
||||||
// We're higher than the remote! Odd
|
|
||||||
{1000, 1500,
|
|
||||||
[]int{997, 999},
|
|
||||||
},
|
|
||||||
// Check some weird edgecases that it behaves somewhat rationally
|
|
||||||
{0, 1500,
|
|
||||||
[]int{0, 2},
|
|
||||||
},
|
|
||||||
{6000000, 0,
|
|
||||||
[]int{5999823, 5999839, 5999855, 5999871, 5999887, 5999903, 5999919, 5999935, 5999951, 5999967, 5999983, 5999999},
|
|
||||||
},
|
|
||||||
{0, 0,
|
|
||||||
[]int{0, 2},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
reqs := func(from, count, span int) []int {
|
|
||||||
var r []int
|
|
||||||
num := from
|
|
||||||
for len(r) < count {
|
|
||||||
r = append(r, num)
|
|
||||||
num += span + 1
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
for i, tt := range testCases {
|
|
||||||
from, count, span, max := calculateRequestSpan(tt.remoteHeight, tt.localHeight)
|
|
||||||
data := reqs(int(from), count, span)
|
|
||||||
|
|
||||||
if max != uint64(data[len(data)-1]) {
|
|
||||||
t.Errorf("test %d: wrong last value %d != %d", i, data[len(data)-1], max)
|
|
||||||
}
|
|
||||||
failed := false
|
|
||||||
if len(data) != len(tt.expected) {
|
|
||||||
failed = true
|
|
||||||
t.Errorf("test %d: length wrong, expected %d got %d", i, len(tt.expected), len(data))
|
|
||||||
} else {
|
|
||||||
for j, n := range data {
|
|
||||||
if n != tt.expected[j] {
|
|
||||||
failed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if failed {
|
|
||||||
res := strings.ReplaceAll(fmt.Sprint(data), " ", ",")
|
|
||||||
exp := strings.ReplaceAll(fmt.Sprint(tt.expected), " ", ",")
|
|
||||||
t.Logf("got: %v\n", res)
|
|
||||||
t.Logf("exp: %v\n", exp)
|
|
||||||
t.Errorf("test %d: wrong values", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that peers below a pre-configured checkpoint block are prevented from
|
// Tests that peers below a pre-configured checkpoint block are prevented from
|
||||||
// being fast-synced from, avoiding potential cheap eclipse attacks.
|
// being fast-synced from, avoiding potential cheap eclipse attacks.
|
||||||
func TestBeaconSync68Full(t *testing.T) { testBeaconSync(t, eth.ETH68, FullSync) }
|
func TestBeaconSync68Full(t *testing.T) { testBeaconSync(t, eth.ETH68, FullSync) }
|
||||||
func TestBeaconSync68Snap(t *testing.T) { testBeaconSync(t, eth.ETH68, SnapSync) }
|
func TestBeaconSync68Snap(t *testing.T) { testBeaconSync(t, eth.ETH68, SnapSync) }
|
||||||
|
|
||||||
func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) {
|
func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
|
||||||
|
|
||||||
var cases = []struct {
|
var cases = []struct {
|
||||||
name string // The name of testing scenario
|
name string // The name of testing scenario
|
||||||
local int // The length of local chain(canonical chain assumed), 0 means genesis is the head
|
local int // The length of local chain(canonical chain assumed), 0 means genesis is the head
|
||||||
|
|
@ -1311,3 +691,70 @@ func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that synchronisation progress (origin block number, current block number
|
||||||
|
// and highest block number) is tracked and updated correctly.
|
||||||
|
func TestSyncProgress68Full(t *testing.T) { testSyncProgress(t, eth.ETH68, FullSync) }
|
||||||
|
func TestSyncProgress68Snap(t *testing.T) { testSyncProgress(t, eth.ETH68, SnapSync) }
|
||||||
|
func TestSyncProgress68Light(t *testing.T) { testSyncProgress(t, eth.ETH68, LightSync) }
|
||||||
|
|
||||||
|
func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
|
success := make(chan struct{})
|
||||||
|
tester := newTesterWithNotification(t, func() {
|
||||||
|
success <- struct{}{}
|
||||||
|
})
|
||||||
|
defer tester.terminate()
|
||||||
|
checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{})
|
||||||
|
|
||||||
|
chain := testChainBase.shorten(blockCacheMaxItems - 15)
|
||||||
|
shortChain := chain.shorten(len(chain.blocks) / 2).blocks[1:]
|
||||||
|
|
||||||
|
// Connect to peer that provides all headers and part of the bodies
|
||||||
|
faultyPeer := tester.newPeer("peer-half", protocol, shortChain)
|
||||||
|
for _, header := range shortChain {
|
||||||
|
faultyPeer.withholdBodies[header.Hash()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)/2-1].Header(), nil); err != nil {
|
||||||
|
t.Fatalf("failed to beacon-sync chain: %v", err)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-success:
|
||||||
|
// Ok, downloader fully cancelled after sync cycle
|
||||||
|
checkProgress(t, tester.downloader, "peer-half", ethereum.SyncProgress{
|
||||||
|
CurrentBlock: uint64(len(chain.blocks)/2 - 1),
|
||||||
|
HighestBlock: uint64(len(chain.blocks)/2 - 1),
|
||||||
|
})
|
||||||
|
case <-time.NewTimer(time.Second * 3).C:
|
||||||
|
t.Fatalf("Failed to sync chain in three seconds")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronise all the blocks and check continuation progress
|
||||||
|
tester.newPeer("peer-full", protocol, chain.blocks[1:])
|
||||||
|
if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
|
||||||
|
t.Fatalf("failed to beacon-sync chain: %v", err)
|
||||||
|
}
|
||||||
|
var startingBlock uint64
|
||||||
|
if mode == LightSync {
|
||||||
|
// in light-sync mode:
|
||||||
|
// * the starting block is 0 on the second sync cycle because blocks
|
||||||
|
// are never downloaded.
|
||||||
|
// * The current/highest blocks reported in the progress reflect the
|
||||||
|
// current/highest header.
|
||||||
|
startingBlock = 0
|
||||||
|
} else {
|
||||||
|
startingBlock = uint64(len(chain.blocks)/2 - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-success:
|
||||||
|
// Ok, downloader fully cancelled after sync cycle
|
||||||
|
checkProgress(t, tester.downloader, "peer-full", ethereum.SyncProgress{
|
||||||
|
StartingBlock: startingBlock,
|
||||||
|
CurrentBlock: uint64(len(chain.blocks) - 1),
|
||||||
|
HighestBlock: uint64(len(chain.blocks) - 1),
|
||||||
|
})
|
||||||
|
case <-time.NewTimer(time.Second * 3).C:
|
||||||
|
t.Fatalf("Failed to sync chain in three seconds")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -1132,6 +1132,16 @@ func (s *skeleton) cleanStales(filled *types.Header) error {
|
||||||
if number+1 == s.progress.Subchains[0].Tail {
|
if number+1 == s.progress.Subchains[0].Tail {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// If the latest fill was on a different subchain, it means the backfiller
|
||||||
|
// was interrupted before it got to do any meaningful work, no cleanup
|
||||||
|
header := rawdb.ReadSkeletonHeader(s.db, filled.Number.Uint64())
|
||||||
|
if header == nil {
|
||||||
|
log.Debug("Filled header outside of skeleton range", "number", number, "head", s.progress.Subchains[0].Head, "tail", s.progress.Subchains[0].Tail)
|
||||||
|
return nil
|
||||||
|
} else if header.Hash() != filled.Hash() {
|
||||||
|
log.Debug("Filled header on different sidechain", "number", number, "filled", filled.Hash(), "skeleton", header.Hash())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
start uint64
|
start uint64
|
||||||
end uint64
|
end uint64
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ type Config struct {
|
||||||
|
|
||||||
// Enables VM tracing
|
// Enables VM tracing
|
||||||
VMTrace string
|
VMTrace string
|
||||||
VMTraceConfig string
|
VMTraceJsonConfig string
|
||||||
|
|
||||||
// Miscellaneous options
|
// Miscellaneous options
|
||||||
DocRoot string `toml:"-"`
|
DocRoot string `toml:"-"`
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
GPO gasprice.Config
|
GPO gasprice.Config
|
||||||
EnablePreimageRecording bool
|
EnablePreimageRecording bool
|
||||||
VMTrace string
|
VMTrace string
|
||||||
VMTraceConfig string
|
VMTraceJsonConfig string
|
||||||
DocRoot string `toml:"-"`
|
DocRoot string `toml:"-"`
|
||||||
RPCGasCap uint64
|
RPCGasCap uint64
|
||||||
RPCEVMTimeout time.Duration
|
RPCEVMTimeout time.Duration
|
||||||
|
|
@ -94,7 +94,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
enc.GPO = c.GPO
|
enc.GPO = c.GPO
|
||||||
enc.EnablePreimageRecording = c.EnablePreimageRecording
|
enc.EnablePreimageRecording = c.EnablePreimageRecording
|
||||||
enc.VMTrace = c.VMTrace
|
enc.VMTrace = c.VMTrace
|
||||||
enc.VMTraceConfig = c.VMTraceConfig
|
enc.VMTraceJsonConfig = c.VMTraceJsonConfig
|
||||||
enc.DocRoot = c.DocRoot
|
enc.DocRoot = c.DocRoot
|
||||||
enc.RPCGasCap = c.RPCGasCap
|
enc.RPCGasCap = c.RPCGasCap
|
||||||
enc.RPCEVMTimeout = c.RPCEVMTimeout
|
enc.RPCEVMTimeout = c.RPCEVMTimeout
|
||||||
|
|
@ -141,7 +141,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
GPO *gasprice.Config
|
GPO *gasprice.Config
|
||||||
EnablePreimageRecording *bool
|
EnablePreimageRecording *bool
|
||||||
VMTrace *string
|
VMTrace *string
|
||||||
VMTraceConfig *string
|
VMTraceJsonConfig *string
|
||||||
DocRoot *string `toml:"-"`
|
DocRoot *string `toml:"-"`
|
||||||
RPCGasCap *uint64
|
RPCGasCap *uint64
|
||||||
RPCEVMTimeout *time.Duration
|
RPCEVMTimeout *time.Duration
|
||||||
|
|
@ -255,8 +255,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
if dec.VMTrace != nil {
|
if dec.VMTrace != nil {
|
||||||
c.VMTrace = *dec.VMTrace
|
c.VMTrace = *dec.VMTrace
|
||||||
}
|
}
|
||||||
if dec.VMTraceConfig != nil {
|
if dec.VMTraceJsonConfig != nil {
|
||||||
c.VMTraceConfig = *dec.VMTraceConfig
|
c.VMTraceJsonConfig = *dec.VMTraceJsonConfig
|
||||||
}
|
}
|
||||||
if dec.DocRoot != nil {
|
if dec.DocRoot != nil {
|
||||||
c.DocRoot = *dec.DocRoot
|
c.DocRoot = *dec.DocRoot
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue