mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-12 15:03:45 +00:00
Merge branch 'geth-bor' into geth-bor-changes
This commit is contained in:
commit
c241e81ea3
97 changed files with 4805 additions and 1034 deletions
11
.github/CODEOWNERS
vendored
11
.github/CODEOWNERS
vendored
|
|
@ -3,21 +3,20 @@
|
||||||
|
|
||||||
accounts/usbwallet @karalabe
|
accounts/usbwallet @karalabe
|
||||||
accounts/scwallet @gballet
|
accounts/scwallet @gballet
|
||||||
accounts/abi @gballet
|
accounts/abi @gballet @MariusVanDerWijden
|
||||||
cmd/clef @holiman
|
cmd/clef @holiman
|
||||||
cmd/puppeth @karalabe
|
cmd/puppeth @karalabe
|
||||||
consensus @karalabe
|
consensus @karalabe
|
||||||
core/ @karalabe @holiman @rjl493456442
|
core/ @karalabe @holiman @rjl493456442
|
||||||
dashboard/ @kurkomisi
|
|
||||||
eth/ @karalabe @holiman @rjl493456442
|
eth/ @karalabe @holiman @rjl493456442
|
||||||
graphql/ @gballet
|
graphql/ @gballet
|
||||||
les/ @zsfelfoldi @rjl493456442
|
les/ @zsfelfoldi @rjl493456442
|
||||||
light/ @zsfelfoldi @rjl493456442
|
light/ @zsfelfoldi @rjl493456442
|
||||||
mobile/ @karalabe @ligi
|
mobile/ @karalabe @ligi
|
||||||
|
node/ @fjl @renaynay
|
||||||
p2p/ @fjl @zsfelfoldi
|
p2p/ @fjl @zsfelfoldi
|
||||||
rpc/ @fjl @holiman
|
rpc/ @fjl @holiman
|
||||||
p2p/simulations @zelig @janos @justelad
|
p2p/simulations @fjl
|
||||||
p2p/protocols @zelig @janos @justelad
|
p2p/protocols @fjl
|
||||||
p2p/testing @zelig @janos @justelad
|
p2p/testing @fjl
|
||||||
signer/ @holiman
|
signer/ @holiman
|
||||||
whisper/ @gballet @gluk256
|
|
||||||
|
|
|
||||||
112
.travis.yml
112
.travis.yml
|
|
@ -16,7 +16,7 @@ jobs:
|
||||||
- stage: lint
|
- stage: lint
|
||||||
os: linux
|
os: linux
|
||||||
dist: xenial
|
dist: xenial
|
||||||
go: 1.14.x
|
go: 1.15.x
|
||||||
env:
|
env:
|
||||||
- lint
|
- lint
|
||||||
git:
|
git:
|
||||||
|
|
@ -24,65 +24,12 @@ jobs:
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go lint
|
- go run build/ci.go lint
|
||||||
|
|
||||||
- stage: build
|
|
||||||
os: linux
|
|
||||||
dist: xenial
|
|
||||||
go: 1.13.x
|
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
script:
|
|
||||||
- go run build/ci.go install
|
|
||||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
|
||||||
|
|
||||||
# These are the latest Go versions.
|
|
||||||
- stage: build
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
dist: xenial
|
|
||||||
go: 1.14.x
|
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
script:
|
|
||||||
- go run build/ci.go install
|
|
||||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
|
||||||
|
|
||||||
- stage: build
|
|
||||||
if: type = pull_request
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
dist: xenial
|
|
||||||
go: 1.14.x
|
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
script:
|
|
||||||
- go run build/ci.go install
|
|
||||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
|
||||||
|
|
||||||
- stage: build
|
|
||||||
os: osx
|
|
||||||
osx_image: xcode11.3
|
|
||||||
go: 1.14.x
|
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
script:
|
|
||||||
- echo "Increase the maximum number of open file descriptors on macOS"
|
|
||||||
- NOFILE=20480
|
|
||||||
- sudo sysctl -w kern.maxfiles=$NOFILE
|
|
||||||
- sudo sysctl -w kern.maxfilesperproc=$NOFILE
|
|
||||||
- sudo launchctl limit maxfiles $NOFILE $NOFILE
|
|
||||||
- sudo launchctl limit maxfiles
|
|
||||||
- ulimit -S -n $NOFILE
|
|
||||||
- ulimit -n
|
|
||||||
- unset -f cd # workaround for https://github.com/travis-ci/travis-ci/issues/8703
|
|
||||||
- go run build/ci.go install
|
|
||||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
|
||||||
|
|
||||||
# This builder does the Ubuntu PPA upload
|
# This builder does the Ubuntu PPA upload
|
||||||
- stage: build
|
- stage: build
|
||||||
if: type = push
|
if: type = push
|
||||||
os: linux
|
os: linux
|
||||||
dist: xenial
|
dist: xenial
|
||||||
go: 1.14.x
|
go: 1.15.x
|
||||||
env:
|
env:
|
||||||
- ubuntu-ppa
|
- ubuntu-ppa
|
||||||
- GO111MODULE=on
|
- GO111MODULE=on
|
||||||
|
|
@ -107,7 +54,7 @@ jobs:
|
||||||
os: linux
|
os: linux
|
||||||
dist: xenial
|
dist: xenial
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.14.x
|
go: 1.15.x
|
||||||
env:
|
env:
|
||||||
- azure-linux
|
- azure-linux
|
||||||
- GO111MODULE=on
|
- GO111MODULE=on
|
||||||
|
|
@ -119,22 +66,22 @@ jobs:
|
||||||
- gcc-multilib
|
- gcc-multilib
|
||||||
script:
|
script:
|
||||||
# Build for the primary platforms that Trusty can manage
|
# Build for the primary platforms that Trusty can manage
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install -dlgo
|
||||||
- go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
- go run build/ci.go install -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 -upload gethstore/builds
|
- go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
||||||
# Switch over GCC to cross compilation (breaks 386, hence why do it here only)
|
# Switch over GCC to cross compilation (breaks 386, hence why do it here only)
|
||||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross
|
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross
|
||||||
- sudo ln -s /usr/include/asm-generic /usr/include/asm
|
- sudo ln -s /usr/include/asm-generic /usr/include/asm
|
||||||
|
|
||||||
- GOARM=5 go run build/ci.go install -arch arm -cc arm-linux-gnueabi-gcc
|
- GOARM=5 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc
|
||||||
- GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
- GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
- GOARM=6 go run build/ci.go install -arch arm -cc arm-linux-gnueabi-gcc
|
- GOARM=6 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc
|
||||||
- GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
- GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
- GOARM=7 go run build/ci.go install -arch arm -cc arm-linux-gnueabihf-gcc
|
- GOARM=7 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabihf-gcc
|
||||||
- GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
- GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
- go run build/ci.go install -arch arm64 -cc aarch64-linux-gnu-gcc
|
- go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc
|
||||||
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
||||||
# This builder does the Linux Azure MIPS xgo uploads
|
# This builder does the Linux Azure MIPS xgo uploads
|
||||||
|
|
@ -144,7 +91,7 @@ jobs:
|
||||||
dist: xenial
|
dist: xenial
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
go: 1.14.x
|
go: 1.15.x
|
||||||
env:
|
env:
|
||||||
- azure-linux-mips
|
- azure-linux-mips
|
||||||
- GO111MODULE=on
|
- GO111MODULE=on
|
||||||
|
|
@ -192,7 +139,7 @@ jobs:
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
before_install:
|
before_install:
|
||||||
- curl https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz | tar -xz
|
- curl https://dl.google.com/go/go1.15.5.linux-amd64.tar.gz | tar -xz
|
||||||
- export PATH=`pwd`/go/bin:$PATH
|
- export PATH=`pwd`/go/bin:$PATH
|
||||||
- export GOROOT=`pwd`/go
|
- export GOROOT=`pwd`/go
|
||||||
- export GOPATH=$HOME/go
|
- export GOPATH=$HOME/go
|
||||||
|
|
@ -210,7 +157,7 @@ jobs:
|
||||||
- stage: build
|
- stage: build
|
||||||
if: type = push
|
if: type = push
|
||||||
os: osx
|
os: osx
|
||||||
go: 1.14.x
|
go: 1.15.x
|
||||||
env:
|
env:
|
||||||
- azure-osx
|
- azure-osx
|
||||||
- azure-ios
|
- azure-ios
|
||||||
|
|
@ -219,7 +166,7 @@ jobs:
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install -dlgo
|
||||||
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
||||||
# Build the iOS framework and upload it to CocoaPods and Azure
|
# Build the iOS framework and upload it to CocoaPods and Azure
|
||||||
|
|
@ -237,12 +184,43 @@ jobs:
|
||||||
- export CGO_CFLAGS_ALLOW='-fmodules|-fblocks|-fobjc-arc'
|
- export CGO_CFLAGS_ALLOW='-fmodules|-fblocks|-fobjc-arc'
|
||||||
- go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds
|
- go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds
|
||||||
|
|
||||||
|
# These builders run the tests
|
||||||
|
- stage: build
|
||||||
|
os: linux
|
||||||
|
arch: amd64
|
||||||
|
dist: xenial
|
||||||
|
go: 1.15.x
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
script:
|
||||||
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
|
- stage: build
|
||||||
|
if: type = pull_request
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
dist: xenial
|
||||||
|
go: 1.15.x
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
script:
|
||||||
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
|
- stage: build
|
||||||
|
os: linux
|
||||||
|
dist: xenial
|
||||||
|
go: 1.14.x
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
script:
|
||||||
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
# This builder does the Azure archive purges to avoid accumulating junk
|
# This builder does the Azure archive purges to avoid accumulating junk
|
||||||
- stage: build
|
- stage: build
|
||||||
if: type = cron
|
if: type = cron
|
||||||
os: linux
|
os: linux
|
||||||
dist: xenial
|
dist: xenial
|
||||||
go: 1.14.x
|
go: 1.15.x
|
||||||
env:
|
env:
|
||||||
- azure-purge
|
- azure-purge
|
||||||
- GO111MODULE=on
|
- GO111MODULE=on
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,10 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
|
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
|
||||||
if err == nil && len(output) == 0 {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(output) == 0 {
|
||||||
// Make sure we have a contract to operate on, and bail out otherwise.
|
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||||
if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil {
|
if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio
|
||||||
return common.Address{}, nil, accounts.ErrWalletClosed
|
return common.Address{}, nil, accounts.ErrWalletClosed
|
||||||
}
|
}
|
||||||
// Ensure the wallet is capable of signing the given transaction
|
// Ensure the wallet is capable of signing the given transaction
|
||||||
if chainID != nil && w.version[0] <= 1 && w.version[2] <= 2 {
|
if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 {
|
||||||
//lint:ignore ST1005 brand name displayed on the console
|
//lint:ignore ST1005 brand name displayed on the console
|
||||||
return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2])
|
return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,13 @@ environment:
|
||||||
install:
|
install:
|
||||||
- git submodule update --init
|
- git submodule update --init
|
||||||
- rmdir C:\go /s /q
|
- rmdir C:\go /s /q
|
||||||
- appveyor DownloadFile https://dl.google.com/go/go1.15.windows-%GETH_ARCH%.zip
|
- appveyor DownloadFile https://dl.google.com/go/go1.15.5.windows-%GETH_ARCH%.zip
|
||||||
- 7z x go1.15.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
- 7z x go1.15.5.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||||
- go version
|
- go version
|
||||||
- gcc --version
|
- gcc --version
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- go run build\ci.go install
|
- go run build\ci.go install -dlgo
|
||||||
|
|
||||||
after_build:
|
after_build:
|
||||||
- go run build\ci.go archive -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
- go run build\ci.go archive -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
# This file contains sha256 checksums of optional build dependencies.
|
# This file contains sha256 checksums of optional build dependencies.
|
||||||
|
|
||||||
69438f7ed4f532154ffaf878f3dfd83747e7a00b70b3556eddabf7aaee28ac3a go1.15.src.tar.gz
|
c1076b90cf94b73ebed62a81d802cd84d43d02dea8c07abdc922c57a071c84f1 go1.15.5.src.tar.gz
|
||||||
|
359a4334b8c8f5e3067e5a76f16419791ac3fef4613d8e8e1eac0b9719915f6d go1.15.5.darwin-amd64.tar.gz
|
||||||
|
4c8179d406136979724c71732009c7e2e7c794dbeaaa2a043c00da34d4be0559 go1.15.5.linux-386.tar.gz
|
||||||
|
9a58494e8da722c3aef248c9227b0e9c528c7318309827780f16220998180a0d go1.15.5.linux-amd64.tar.gz
|
||||||
|
a72a0b036beb4193a0214bca3fca4c5d68a38a4ccf098c909f7ce8bf08567c48 go1.15.5.linux-arm64.tar.gz
|
||||||
|
5ea6456620d3efed5dda99238c7f23866eafdd915e5348736e631bc283c0238a go1.15.5.linux-armv6l.tar.gz
|
||||||
|
d812436c7e3482ba3c97172edf26afaf35aca60a5621ff4a5f6a08386505ab9c go1.15.5.windows-386.zip
|
||||||
|
1d24be3a200201a74be25e4134fbec467750e834e84e9c7789a9fc13248c5507 go1.15.5.windows-amd64.zip
|
||||||
|
|
||||||
d998a84eea42f2271aca792a7b027ca5c1edfcba229e8e5a844c9ac3f336df35 golangci-lint-1.27.0-linux-armv7.tar.gz
|
d998a84eea42f2271aca792a7b027ca5c1edfcba229e8e5a844c9ac3f336df35 golangci-lint-1.27.0-linux-armv7.tar.gz
|
||||||
bf781f05b0d393b4bf0a327d9e62926949a4f14d7774d950c4e009fc766ed1d4 golangci-lint.exe-1.27.0-windows-amd64.zip
|
bf781f05b0d393b4bf0a327d9e62926949a4f14d7774d950c4e009fc766ed1d4 golangci-lint.exe-1.27.0-windows-amd64.zip
|
||||||
|
|
|
||||||
203
build/ci.go
203
build/ci.go
|
|
@ -46,12 +46,11 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
@ -135,11 +134,11 @@ var (
|
||||||
// Note: artful is unsupported because it was officially deprecated on Launchpad.
|
// Note: artful is unsupported because it was officially deprecated on Launchpad.
|
||||||
// Note: cosmic is unsupported because it was officially deprecated on Launchpad.
|
// Note: cosmic is unsupported because it was officially deprecated on Launchpad.
|
||||||
// Note: disco is unsupported because it was officially deprecated on Launchpad.
|
// Note: disco is unsupported because it was officially deprecated on Launchpad.
|
||||||
|
// Note: eoan is unsupported because it was officially deprecated on Launchpad.
|
||||||
debDistroGoBoots = map[string]string{
|
debDistroGoBoots = map[string]string{
|
||||||
"trusty": "golang-1.11",
|
"trusty": "golang-1.11",
|
||||||
"xenial": "golang-go",
|
"xenial": "golang-go",
|
||||||
"bionic": "golang-go",
|
"bionic": "golang-go",
|
||||||
"eoan": "golang-go",
|
|
||||||
"focal": "golang-go",
|
"focal": "golang-go",
|
||||||
"groovy": "golang-go",
|
"groovy": "golang-go",
|
||||||
}
|
}
|
||||||
|
|
@ -148,6 +147,11 @@ var (
|
||||||
"golang-1.11": "/usr/lib/go-1.11",
|
"golang-1.11": "/usr/lib/go-1.11",
|
||||||
"golang-go": "/usr/lib/go",
|
"golang-go": "/usr/lib/go",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is the version of go that will be downloaded by
|
||||||
|
//
|
||||||
|
// go run ci.go install -dlgo
|
||||||
|
dlgoVersion = "1.15.5"
|
||||||
)
|
)
|
||||||
|
|
||||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||||
|
|
@ -198,19 +202,19 @@ func main() {
|
||||||
|
|
||||||
func doInstall(cmdline []string) {
|
func doInstall(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
|
dlgo = flag.Bool("dlgo", false, "Download Go and build with it")
|
||||||
arch = flag.String("arch", "", "Architecture to cross build for")
|
arch = flag.String("arch", "", "Architecture to cross build for")
|
||||||
cc = flag.String("cc", "", "C compiler to cross build with")
|
cc = flag.String("cc", "", "C compiler to cross build with")
|
||||||
)
|
)
|
||||||
flag.CommandLine.Parse(cmdline)
|
flag.CommandLine.Parse(cmdline)
|
||||||
env := build.Env()
|
env := build.Env()
|
||||||
|
|
||||||
// Check Go version. People regularly open issues about compilation
|
// Check local Go version. People regularly open issues about compilation
|
||||||
// failure with outdated Go. This should save them the trouble.
|
// failure with outdated Go. This should save them the trouble.
|
||||||
if !strings.Contains(runtime.Version(), "devel") {
|
if !strings.Contains(runtime.Version(), "devel") {
|
||||||
// Figure out the minor version number since we can't textually compare (1.10 < 1.9)
|
// Figure out the minor version number since we can't textually compare (1.10 < 1.9)
|
||||||
var minor int
|
var minor int
|
||||||
fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor)
|
fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor)
|
||||||
|
|
||||||
if minor < 13 {
|
if minor < 13 {
|
||||||
log.Println("You have Go version", runtime.Version())
|
log.Println("You have Go version", runtime.Version())
|
||||||
log.Println("go-ethereum requires at least Go version 1.13 and cannot")
|
log.Println("go-ethereum requires at least Go version 1.13 and cannot")
|
||||||
|
|
@ -218,90 +222,110 @@ func doInstall(cmdline []string) {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Compile packages given as arguments, or everything if there are no arguments.
|
|
||||||
packages := []string{"./..."}
|
// Choose which go command we're going to use.
|
||||||
if flag.NArg() > 0 {
|
var gobuild *exec.Cmd
|
||||||
packages = flag.Args()
|
if !*dlgo {
|
||||||
|
// Default behavior: use the go version which runs ci.go right now.
|
||||||
|
gobuild = goTool("build")
|
||||||
|
} else {
|
||||||
|
// Download of Go requested. This is for build environments where the
|
||||||
|
// installed version is too old and cannot be upgraded easily.
|
||||||
|
cachedir := filepath.Join("build", "cache")
|
||||||
|
goroot := downloadGo(runtime.GOARCH, runtime.GOOS, cachedir)
|
||||||
|
gobuild = localGoTool(goroot, "build")
|
||||||
}
|
}
|
||||||
|
|
||||||
if *arch == "" || *arch == runtime.GOARCH {
|
// Configure environment for cross build.
|
||||||
goinstall := goTool("install", buildFlags(env)...)
|
if *arch != "" || *arch != runtime.GOARCH {
|
||||||
if runtime.GOARCH == "arm64" {
|
gobuild.Env = append(gobuild.Env, "CGO_ENABLED=1")
|
||||||
goinstall.Args = append(goinstall.Args, "-p", "1")
|
gobuild.Env = append(gobuild.Env, "GOARCH="+*arch)
|
||||||
}
|
|
||||||
goinstall.Args = append(goinstall.Args, "-trimpath")
|
|
||||||
goinstall.Args = append(goinstall.Args, "-v")
|
|
||||||
goinstall.Args = append(goinstall.Args, packages...)
|
|
||||||
build.MustRun(goinstall)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seems we are cross compiling, work around forbidden GOBIN
|
// Configure C compiler.
|
||||||
goinstall := goToolArch(*arch, *cc, "install", buildFlags(env)...)
|
if *cc != "" {
|
||||||
goinstall.Args = append(goinstall.Args, "-trimpath")
|
gobuild.Env = append(gobuild.Env, "CC="+*cc)
|
||||||
goinstall.Args = append(goinstall.Args, "-v")
|
} else if os.Getenv("CC") != "" {
|
||||||
goinstall.Args = append(goinstall.Args, []string{"-buildmode", "archive"}...)
|
gobuild.Env = append(gobuild.Env, "CC="+os.Getenv("CC"))
|
||||||
goinstall.Args = append(goinstall.Args, packages...)
|
}
|
||||||
build.MustRun(goinstall)
|
|
||||||
|
|
||||||
if cmds, err := ioutil.ReadDir("cmd"); err == nil {
|
// arm64 CI builders are memory-constrained and can't handle concurrent builds,
|
||||||
for _, cmd := range cmds {
|
// better disable it. This check isn't the best, it should probably
|
||||||
pkgs, err := parser.ParseDir(token.NewFileSet(), filepath.Join(".", "cmd", cmd.Name()), nil, parser.PackageClauseOnly)
|
// check for something in env instead.
|
||||||
if err != nil {
|
if runtime.GOARCH == "arm64" {
|
||||||
log.Fatal(err)
|
gobuild.Args = append(gobuild.Args, "-p", "1")
|
||||||
}
|
}
|
||||||
for name := range pkgs {
|
|
||||||
if name == "main" {
|
// Put the default settings in.
|
||||||
gobuild := goToolArch(*arch, *cc, "build", buildFlags(env)...)
|
gobuild.Args = append(gobuild.Args, buildFlags(env)...)
|
||||||
gobuild.Args = append(gobuild.Args, "-v")
|
|
||||||
gobuild.Args = append(gobuild.Args, []string{"-o", executablePath(cmd.Name())}...)
|
// We use -trimpath to avoid leaking local paths into the built executables.
|
||||||
gobuild.Args = append(gobuild.Args, "."+string(filepath.Separator)+filepath.Join("cmd", cmd.Name()))
|
gobuild.Args = append(gobuild.Args, "-trimpath")
|
||||||
build.MustRun(gobuild)
|
|
||||||
break
|
// Show packages during build.
|
||||||
}
|
gobuild.Args = append(gobuild.Args, "-v")
|
||||||
}
|
|
||||||
}
|
// Now we choose what we're even building.
|
||||||
|
// Default: collect all 'main' packages in cmd/ and build those.
|
||||||
|
packages := flag.Args()
|
||||||
|
if len(packages) == 0 {
|
||||||
|
packages = build.FindMainPackages("./cmd")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the build!
|
||||||
|
for _, pkg := range packages {
|
||||||
|
args := make([]string, len(gobuild.Args))
|
||||||
|
copy(args, gobuild.Args)
|
||||||
|
args = append(args, "-o", executablePath(path.Base(pkg)))
|
||||||
|
args = append(args, pkg)
|
||||||
|
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildFlags returns the go tool flags for building.
|
||||||
func buildFlags(env build.Environment) (flags []string) {
|
func buildFlags(env build.Environment) (flags []string) {
|
||||||
var ld []string
|
var ld []string
|
||||||
if env.Commit != "" {
|
if env.Commit != "" {
|
||||||
ld = append(ld, "-X", "main.gitCommit="+env.Commit)
|
ld = append(ld, "-X", "main.gitCommit="+env.Commit)
|
||||||
ld = append(ld, "-X", "main.gitDate="+env.Date)
|
ld = append(ld, "-X", "main.gitDate="+env.Date)
|
||||||
}
|
}
|
||||||
|
// Strip DWARF on darwin. This used to be required for certain things,
|
||||||
|
// and there is no downside to this, so we just keep doing it.
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
ld = append(ld, "-s")
|
ld = append(ld, "-s")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ld) > 0 {
|
if len(ld) > 0 {
|
||||||
flags = append(flags, "-ldflags", strings.Join(ld, " "))
|
flags = append(flags, "-ldflags", strings.Join(ld, " "))
|
||||||
}
|
}
|
||||||
return flags
|
return flags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// goTool returns the go tool. This uses the Go version which runs ci.go.
|
||||||
func goTool(subcmd string, args ...string) *exec.Cmd {
|
func goTool(subcmd string, args ...string) *exec.Cmd {
|
||||||
return goToolArch(runtime.GOARCH, os.Getenv("CC"), subcmd, args...)
|
cmd := build.GoTool(subcmd, args...)
|
||||||
|
goToolSetEnv(cmd)
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func goToolArch(arch string, cc string, subcmd string, args ...string) *exec.Cmd {
|
// localGoTool returns the go tool from the given GOROOT.
|
||||||
cmd := build.GoTool(subcmd, args...)
|
func localGoTool(goroot string, subcmd string, args ...string) *exec.Cmd {
|
||||||
if arch == "" || arch == runtime.GOARCH {
|
gotool := filepath.Join(goroot, "bin", "go")
|
||||||
cmd.Env = append(cmd.Env, "GOBIN="+GOBIN)
|
cmd := exec.Command(gotool, subcmd)
|
||||||
} else {
|
goToolSetEnv(cmd)
|
||||||
cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
|
cmd.Env = append(cmd.Env, "GOROOT="+goroot)
|
||||||
cmd.Env = append(cmd.Env, "GOARCH="+arch)
|
cmd.Args = append(cmd.Args, args...)
|
||||||
}
|
return cmd
|
||||||
if cc != "" {
|
}
|
||||||
cmd.Env = append(cmd.Env, "CC="+cc)
|
|
||||||
}
|
// goToolSetEnv forwards the build environment to the go tool.
|
||||||
|
func goToolSetEnv(cmd *exec.Cmd) {
|
||||||
|
cmd.Env = append(cmd.Env, "GOBIN="+GOBIN)
|
||||||
for _, e := range os.Environ() {
|
for _, e := range os.Environ() {
|
||||||
if strings.HasPrefix(e, "GOBIN=") {
|
if strings.HasPrefix(e, "GOBIN=") || strings.HasPrefix(e, "CC=") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cmd.Env = append(cmd.Env, e)
|
cmd.Env = append(cmd.Env, e)
|
||||||
}
|
}
|
||||||
return cmd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Running The Tests
|
// Running The Tests
|
||||||
|
|
@ -363,7 +387,7 @@ func downloadLinter(cachedir string) string {
|
||||||
if err := csdb.DownloadFile(url, archivePath); err != nil {
|
if err := csdb.DownloadFile(url, archivePath); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := build.ExtractTarballArchive(archivePath, cachedir); err != nil {
|
if err := build.ExtractArchive(archivePath, cachedir); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return filepath.Join(cachedir, base, "golangci-lint")
|
return filepath.Join(cachedir, base, "golangci-lint")
|
||||||
|
|
@ -469,13 +493,12 @@ func maybeSkipArchive(env build.Environment) {
|
||||||
// Debian Packaging
|
// Debian Packaging
|
||||||
func doDebianSource(cmdline []string) {
|
func doDebianSource(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
goversion = flag.String("goversion", "", `Go version to build with (will be included in the source package)`)
|
cachedir = flag.String("cachedir", "./build/cache", `Filesystem path to cache the downloaded Go bundles at`)
|
||||||
cachedir = flag.String("cachedir", "./build/cache", `Filesystem path to cache the downloaded Go bundles at`)
|
signer = flag.String("signer", "", `Signing key name, also used as package author`)
|
||||||
signer = flag.String("signer", "", `Signing key name, also used as package author`)
|
upload = flag.String("upload", "", `Where to upload the source package (usually "ethereum/ethereum")`)
|
||||||
upload = flag.String("upload", "", `Where to upload the source package (usually "ethereum/ethereum")`)
|
sshUser = flag.String("sftp-user", "", `Username for SFTP upload (usually "geth-ci")`)
|
||||||
sshUser = flag.String("sftp-user", "", `Username for SFTP upload (usually "geth-ci")`)
|
workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
|
||||||
workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
|
now = time.Now()
|
||||||
now = time.Now()
|
|
||||||
)
|
)
|
||||||
flag.CommandLine.Parse(cmdline)
|
flag.CommandLine.Parse(cmdline)
|
||||||
*workdir = makeWorkdir(*workdir)
|
*workdir = makeWorkdir(*workdir)
|
||||||
|
|
@ -490,10 +513,10 @@ func doDebianSource(cmdline []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download and verify the Go source package.
|
// Download and verify the Go source package.
|
||||||
gobundle := downloadGoSources(*goversion, *cachedir)
|
gobundle := downloadGoSources(*cachedir)
|
||||||
|
|
||||||
// Download all the dependencies needed to build the sources and run the ci script
|
// Download all the dependencies needed to build the sources and run the ci script
|
||||||
srcdepfetch := goTool("install", "-n", "./...")
|
srcdepfetch := goTool("mod", "download")
|
||||||
srcdepfetch.Env = append(os.Environ(), "GOPATH="+filepath.Join(*workdir, "modgopath"))
|
srcdepfetch.Env = append(os.Environ(), "GOPATH="+filepath.Join(*workdir, "modgopath"))
|
||||||
build.MustRun(srcdepfetch)
|
build.MustRun(srcdepfetch)
|
||||||
|
|
||||||
|
|
@ -509,7 +532,7 @@ func doDebianSource(cmdline []string) {
|
||||||
pkgdir := stageDebianSource(*workdir, meta)
|
pkgdir := stageDebianSource(*workdir, meta)
|
||||||
|
|
||||||
// Add Go source code
|
// Add Go source code
|
||||||
if err := build.ExtractTarballArchive(gobundle, pkgdir); err != nil {
|
if err := build.ExtractArchive(gobundle, pkgdir); err != nil {
|
||||||
log.Fatalf("Failed to extract Go sources: %v", err)
|
log.Fatalf("Failed to extract Go sources: %v", err)
|
||||||
}
|
}
|
||||||
if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".go")); err != nil {
|
if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".go")); err != nil {
|
||||||
|
|
@ -541,9 +564,10 @@ func doDebianSource(cmdline []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadGoSources(version string, cachedir string) string {
|
// downloadGoSources downloads the Go source tarball.
|
||||||
|
func downloadGoSources(cachedir string) string {
|
||||||
csdb := build.MustLoadChecksums("build/checksums.txt")
|
csdb := build.MustLoadChecksums("build/checksums.txt")
|
||||||
file := fmt.Sprintf("go%s.src.tar.gz", version)
|
file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion)
|
||||||
url := "https://dl.google.com/go/" + file
|
url := "https://dl.google.com/go/" + file
|
||||||
dst := filepath.Join(cachedir, file)
|
dst := filepath.Join(cachedir, file)
|
||||||
if err := csdb.DownloadFile(url, dst); err != nil {
|
if err := csdb.DownloadFile(url, dst); err != nil {
|
||||||
|
|
@ -552,6 +576,41 @@ func downloadGoSources(version string, cachedir string) string {
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// downloadGo downloads the Go binary distribution and unpacks it into a temporary
|
||||||
|
// directory. It returns the GOROOT of the unpacked toolchain.
|
||||||
|
func downloadGo(goarch, goos, cachedir string) string {
|
||||||
|
if goarch == "arm" {
|
||||||
|
goarch = "armv6l"
|
||||||
|
}
|
||||||
|
|
||||||
|
csdb := build.MustLoadChecksums("build/checksums.txt")
|
||||||
|
file := fmt.Sprintf("go%s.%s-%s", dlgoVersion, goos, goarch)
|
||||||
|
if goos == "windows" {
|
||||||
|
file += ".zip"
|
||||||
|
} else {
|
||||||
|
file += ".tar.gz"
|
||||||
|
}
|
||||||
|
url := "https://golang.org/dl/" + file
|
||||||
|
dst := filepath.Join(cachedir, file)
|
||||||
|
if err := csdb.DownloadFile(url, dst); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ucache, err := os.UserCacheDir()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
godir := filepath.Join(ucache, fmt.Sprintf("geth-go-%s-%s-%s", dlgoVersion, goos, goarch))
|
||||||
|
if err := build.ExtractArchive(dst, godir); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
goroot, err := filepath.Abs(filepath.Join(godir, "go"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return goroot
|
||||||
|
}
|
||||||
|
|
||||||
func ppaUpload(workdir, ppa, sshUser string, files []string) {
|
func ppaUpload(workdir, ppa, sshUser string, files []string) {
|
||||||
p := strings.Split(ppa, "/")
|
p := strings.Split(ppa, "/")
|
||||||
if len(p) != 2 {
|
if len(p) != 2 {
|
||||||
|
|
@ -980,7 +1039,7 @@ func doXCodeFramework(cmdline []string) {
|
||||||
if *deploy != "" {
|
if *deploy != "" {
|
||||||
meta := newPodMetadata(env, archive)
|
meta := newPodMetadata(env, archive)
|
||||||
build.Render("build/pod.podspec", "Geth.podspec", 0755, meta)
|
build.Render("build/pod.podspec", "Geth.podspec", 0755, meta)
|
||||||
build.MustRunCommand("pod", *deploy, "push", "Geth.podspec", "--allow-warnings", "--verbose")
|
build.MustRunCommand("pod", *deploy, "push", "Geth.podspec", "--allow-warnings")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,25 @@ Now get the ENR of your node and store it in the `NODE` environment variable.
|
||||||
|
|
||||||
Start the test by running `devp2p discv5 test -listen1 127.0.0.1 -listen2 127.0.0.2 $NODE`.
|
Start the test by running `devp2p discv5 test -listen1 127.0.0.1 -listen2 127.0.0.2 $NODE`.
|
||||||
|
|
||||||
|
### Eth Protocol Test Suite
|
||||||
|
|
||||||
|
The Eth Protocol test suite is a conformance test suite for the [eth protocol][eth].
|
||||||
|
|
||||||
|
To run the eth protocol test suite against your implementation, the node needs to be initialized as such:
|
||||||
|
|
||||||
|
1. initialize the geth node with the `genesis.json` file contained in the `testdata` directory
|
||||||
|
2. import the `halfchain.rlp` file in the `testdata` directory
|
||||||
|
3. run geth with the following flags:
|
||||||
|
```
|
||||||
|
geth --datadir <datadir> --nodiscover --nat=none --networkid 19763 --verbosity 5
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, run the following command, replacing `<enode ID>` with the enode of the geth node:
|
||||||
|
```
|
||||||
|
devp2p rlpx eth-test <enode ID> cmd/devp2p/internal/ethtest/testdata/fullchain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json
|
||||||
|
```
|
||||||
|
|
||||||
|
[eth]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md
|
||||||
[dns-tutorial]: https://geth.ethereum.org/docs/developers/dns-discovery-setup
|
[dns-tutorial]: https://geth.ethereum.org/docs/developers/dns-discovery-setup
|
||||||
[discv4]: https://github.com/ethereum/devp2p/tree/master/discv4.md
|
[discv4]: https://github.com/ethereum/devp2p/tree/master/discv4.md
|
||||||
[discv5]: https://github.com/ethereum/devp2p/tree/master/discv5/discv5.md
|
[discv5]: https://github.com/ethereum/devp2p/tree/master/discv5/discv5.md
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,12 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test"
|
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -82,7 +80,13 @@ var (
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Usage: "Runs tests against a node",
|
Usage: "Runs tests against a node",
|
||||||
Action: discv4Test,
|
Action: discv4Test,
|
||||||
Flags: []cli.Flag{remoteEnodeFlag, testPatternFlag, testListen1Flag, testListen2Flag},
|
Flags: []cli.Flag{
|
||||||
|
remoteEnodeFlag,
|
||||||
|
testPatternFlag,
|
||||||
|
testTAPFlag,
|
||||||
|
testListen1Flag,
|
||||||
|
testListen2Flag,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -113,20 +117,6 @@ var (
|
||||||
Usage: "Enode of the remote node under test",
|
Usage: "Enode of the remote node under test",
|
||||||
EnvVar: "REMOTE_ENODE",
|
EnvVar: "REMOTE_ENODE",
|
||||||
}
|
}
|
||||||
testPatternFlag = cli.StringFlag{
|
|
||||||
Name: "run",
|
|
||||||
Usage: "Pattern of test suite(s) to run",
|
|
||||||
}
|
|
||||||
testListen1Flag = cli.StringFlag{
|
|
||||||
Name: "listen1",
|
|
||||||
Usage: "IP address of the first tester",
|
|
||||||
Value: v4test.Listen1,
|
|
||||||
}
|
|
||||||
testListen2Flag = cli.StringFlag{
|
|
||||||
Name: "listen2",
|
|
||||||
Usage: "IP address of the second tester",
|
|
||||||
Value: v4test.Listen2,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func discv4Ping(ctx *cli.Context) error {
|
func discv4Ping(ctx *cli.Context) error {
|
||||||
|
|
@ -213,6 +203,7 @@ func discv4Crawl(ctx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// discv4Test runs the protocol test suite.
|
||||||
func discv4Test(ctx *cli.Context) error {
|
func discv4Test(ctx *cli.Context) error {
|
||||||
// Configure test package globals.
|
// Configure test package globals.
|
||||||
if !ctx.IsSet(remoteEnodeFlag.Name) {
|
if !ctx.IsSet(remoteEnodeFlag.Name) {
|
||||||
|
|
@ -221,18 +212,7 @@ func discv4Test(ctx *cli.Context) error {
|
||||||
v4test.Remote = ctx.String(remoteEnodeFlag.Name)
|
v4test.Remote = ctx.String(remoteEnodeFlag.Name)
|
||||||
v4test.Listen1 = ctx.String(testListen1Flag.Name)
|
v4test.Listen1 = ctx.String(testListen1Flag.Name)
|
||||||
v4test.Listen2 = ctx.String(testListen2Flag.Name)
|
v4test.Listen2 = ctx.String(testListen2Flag.Name)
|
||||||
|
return runTests(ctx, v4test.AllTests)
|
||||||
// Filter and run test cases.
|
|
||||||
tests := v4test.AllTests
|
|
||||||
if ctx.IsSet(testPatternFlag.Name) {
|
|
||||||
tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name))
|
|
||||||
}
|
|
||||||
results := utesting.RunTests(tests, os.Stdout)
|
|
||||||
if fails := utesting.CountFailures(results); fails > 0 {
|
|
||||||
return fmt.Errorf("%v/%v tests passed.", len(tests)-fails, len(tests))
|
|
||||||
}
|
|
||||||
fmt.Printf("%v/%v passed\n", len(tests), len(tests))
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// startV4 starts an ephemeral discovery V4 node.
|
// startV4 starts an ephemeral discovery V4 node.
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,10 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v5test"
|
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v5test"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
@ -62,7 +59,12 @@ var (
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Usage: "Runs protocol tests against a node",
|
Usage: "Runs protocol tests against a node",
|
||||||
Action: discv5Test,
|
Action: discv5Test,
|
||||||
Flags: []cli.Flag{testPatternFlag, testListen1Flag, testListen2Flag},
|
Flags: []cli.Flag{
|
||||||
|
testPatternFlag,
|
||||||
|
testTAPFlag,
|
||||||
|
testListen1Flag,
|
||||||
|
testListen2Flag,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
discv5ListenCommand = cli.Command{
|
discv5ListenCommand = cli.Command{
|
||||||
Name: "listen",
|
Name: "listen",
|
||||||
|
|
@ -114,28 +116,14 @@ func discv5Crawl(ctx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// discv5Test runs the protocol test suite.
|
||||||
func discv5Test(ctx *cli.Context) error {
|
func discv5Test(ctx *cli.Context) error {
|
||||||
// Disable logging unless explicitly enabled.
|
|
||||||
if !ctx.GlobalIsSet("verbosity") && !ctx.GlobalIsSet("vmodule") {
|
|
||||||
log.Root().SetHandler(log.DiscardHandler())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter and run test cases.
|
|
||||||
suite := &v5test.Suite{
|
suite := &v5test.Suite{
|
||||||
Dest: getNodeArg(ctx),
|
Dest: getNodeArg(ctx),
|
||||||
Listen1: ctx.String(testListen1Flag.Name),
|
Listen1: ctx.String(testListen1Flag.Name),
|
||||||
Listen2: ctx.String(testListen2Flag.Name),
|
Listen2: ctx.String(testListen2Flag.Name),
|
||||||
}
|
}
|
||||||
tests := suite.AllTests()
|
return runTests(ctx, suite.AllTests())
|
||||||
if ctx.IsSet(testPatternFlag.Name) {
|
|
||||||
tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name))
|
|
||||||
}
|
|
||||||
results := utesting.RunTests(tests, os.Stdout)
|
|
||||||
if fails := utesting.CountFailures(results); fails > 0 {
|
|
||||||
return fmt.Errorf("%v/%v tests passed.", len(tests)-fails, len(tests))
|
|
||||||
}
|
|
||||||
fmt.Printf("%v/%v passed\n", len(tests), len(tests))
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func discv5Listen(ctx *cli.Context) error {
|
func discv5Listen(ctx *cli.Context) error {
|
||||||
|
|
|
||||||
|
|
@ -124,32 +124,6 @@ func (c *Chain) GetHeaders(req GetBlockHeaders) (BlockHeaders, error) {
|
||||||
// loadChain takes the given chain.rlp file, and decodes and returns
|
// loadChain takes the given chain.rlp file, and decodes and returns
|
||||||
// the blocks from the file.
|
// the blocks from the file.
|
||||||
func loadChain(chainfile string, genesis string) (*Chain, error) {
|
func loadChain(chainfile string, genesis string) (*Chain, error) {
|
||||||
// Open the file handle and potentially unwrap the gzip stream
|
|
||||||
fh, err := os.Open(chainfile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer fh.Close()
|
|
||||||
|
|
||||||
var reader io.Reader = fh
|
|
||||||
if strings.HasSuffix(chainfile, ".gz") {
|
|
||||||
if reader, err = gzip.NewReader(reader); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stream := rlp.NewStream(reader, 0)
|
|
||||||
var blocks []*types.Block
|
|
||||||
for i := 0; ; i++ {
|
|
||||||
var b types.Block
|
|
||||||
if err := stream.Decode(&b); err == io.EOF {
|
|
||||||
break
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, fmt.Errorf("at block %d: %v", i, err)
|
|
||||||
}
|
|
||||||
blocks = append(blocks, &b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open the file handle and potentially unwrap the gzip stream
|
|
||||||
chainConfig, err := ioutil.ReadFile(genesis)
|
chainConfig, err := ioutil.ReadFile(genesis)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -158,9 +132,36 @@ func loadChain(chainfile string, genesis string) (*Chain, error) {
|
||||||
if err := json.Unmarshal(chainConfig, &gen); err != nil {
|
if err := json.Unmarshal(chainConfig, &gen); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
gblock := gen.ToBlock(nil)
|
||||||
|
|
||||||
return &Chain{
|
// Load chain.rlp.
|
||||||
blocks: blocks,
|
fh, err := os.Open(chainfile)
|
||||||
chainConfig: gen.Config,
|
if err != nil {
|
||||||
}, nil
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
var reader io.Reader = fh
|
||||||
|
if strings.HasSuffix(chainfile, ".gz") {
|
||||||
|
if reader, err = gzip.NewReader(reader); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream := rlp.NewStream(reader, 0)
|
||||||
|
var blocks = make([]*types.Block, 1)
|
||||||
|
blocks[0] = gblock
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
var b types.Block
|
||||||
|
if err := stream.Decode(&b); err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, fmt.Errorf("at block index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
if b.NumberU64() != uint64(i+1) {
|
||||||
|
return nil, fmt.Errorf("block at index %d has wrong number %d", i, b.NumberU64())
|
||||||
|
}
|
||||||
|
blocks = append(blocks, &b)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &Chain{blocks: blocks, chainConfig: gen.Config}
|
||||||
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ func TestEthProtocolNegotiation(t *testing.T) {
|
||||||
// TestChain_GetHeaders tests whether the test suite can correctly
|
// TestChain_GetHeaders tests whether the test suite can correctly
|
||||||
// respond to a GetBlockHeaders request from a node.
|
// respond to a GetBlockHeaders request from a node.
|
||||||
func TestChain_GetHeaders(t *testing.T) {
|
func TestChain_GetHeaders(t *testing.T) {
|
||||||
chainFile, err := filepath.Abs("./testdata/chain.rlp.gz")
|
chainFile, err := filepath.Abs("./testdata/fullchain.rlp.gz")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@ package ethtest
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
|
@ -27,6 +29,13 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var pretty = spew.ConfigState{
|
||||||
|
Indent: " ",
|
||||||
|
DisableCapacities: true,
|
||||||
|
DisablePointerAddresses: true,
|
||||||
|
SortKeys: true,
|
||||||
|
}
|
||||||
|
|
||||||
// Suite represents a structure used to test the eth
|
// Suite represents a structure used to test the eth
|
||||||
// protocol of a node(s).
|
// protocol of a node(s).
|
||||||
type Suite struct {
|
type Suite struct {
|
||||||
|
|
@ -73,9 +82,9 @@ func (s *Suite) TestStatus(t *utesting.T) {
|
||||||
// get status
|
// get status
|
||||||
switch msg := conn.statusExchange(t, s.chain).(type) {
|
switch msg := conn.statusExchange(t, s.chain).(type) {
|
||||||
case *Status:
|
case *Status:
|
||||||
t.Logf("%+v\n", msg)
|
t.Logf("got status message: %s", pretty.Sdump(msg))
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected: %#v", msg)
|
t.Fatalf("unexpected: %s", pretty.Sdump(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,16 +113,17 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) {
|
||||||
t.Fatalf("could not write to connection: %v", err)
|
t.Fatalf("could not write to connection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg := conn.ReadAndServe(s.chain).(type) {
|
timeout := 20 * time.Second
|
||||||
|
switch msg := conn.ReadAndServe(s.chain, timeout).(type) {
|
||||||
case *BlockHeaders:
|
case *BlockHeaders:
|
||||||
headers := msg
|
headers := msg
|
||||||
for _, header := range *headers {
|
for _, header := range *headers {
|
||||||
num := header.Number.Uint64()
|
num := header.Number.Uint64()
|
||||||
|
t.Logf("received header (%d): %s", num, pretty.Sdump(header))
|
||||||
assert.Equal(t, s.chain.blocks[int(num)].Header(), header)
|
assert.Equal(t, s.chain.blocks[int(num)].Header(), header)
|
||||||
t.Logf("\nHEADER FOR BLOCK NUMBER %d: %+v\n", header.Number, header)
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected: %#v", msg)
|
t.Fatalf("unexpected: %s", pretty.Sdump(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,14 +143,12 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) {
|
||||||
t.Fatalf("could not write to connection: %v", err)
|
t.Fatalf("could not write to connection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg := conn.ReadAndServe(s.chain).(type) {
|
timeout := 20 * time.Second
|
||||||
|
switch msg := conn.ReadAndServe(s.chain, timeout).(type) {
|
||||||
case *BlockBodies:
|
case *BlockBodies:
|
||||||
bodies := msg
|
t.Logf("received %d block bodies", len(*msg))
|
||||||
for _, body := range *bodies {
|
|
||||||
t.Logf("\nBODY: %+v\n", body)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected: %#v", msg)
|
t.Fatalf("unexpected: %s", pretty.Sdump(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,18 +181,27 @@ func (s *Suite) TestBroadcast(t *utesting.T) {
|
||||||
t.Fatalf("could not write to connection: %v", err)
|
t.Fatalf("could not write to connection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg := receiveConn.ReadAndServe(s.chain).(type) {
|
timeout := 20 * time.Second
|
||||||
|
switch msg := receiveConn.ReadAndServe(s.chain, timeout).(type) {
|
||||||
case *NewBlock:
|
case *NewBlock:
|
||||||
assert.Equal(t, blockAnnouncement.Block.Header(), msg.Block.Header(),
|
t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block))
|
||||||
"wrong block header in announcement")
|
assert.Equal(t,
|
||||||
assert.Equal(t, blockAnnouncement.TD, msg.TD,
|
blockAnnouncement.Block.Header(), msg.Block.Header(),
|
||||||
"wrong TD in announcement")
|
"wrong block header in announcement",
|
||||||
|
)
|
||||||
|
assert.Equal(t,
|
||||||
|
blockAnnouncement.TD, msg.TD,
|
||||||
|
"wrong TD in announcement",
|
||||||
|
)
|
||||||
case *NewBlockHashes:
|
case *NewBlockHashes:
|
||||||
hashes := *msg
|
hashes := *msg
|
||||||
assert.Equal(t, blockAnnouncement.Block.Hash(), hashes[0].Hash,
|
t.Logf("received NewBlockHashes message: %s", pretty.Sdump(hashes))
|
||||||
"wrong block hash in announcement")
|
assert.Equal(t,
|
||||||
|
blockAnnouncement.Block.Hash(), hashes[0].Hash,
|
||||||
|
"wrong block hash in announcement",
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected: %#v", msg)
|
t.Fatalf("unexpected: %s", pretty.Sdump(msg))
|
||||||
}
|
}
|
||||||
// update test suite chain
|
// update test suite chain
|
||||||
s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[1000])
|
s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[1000])
|
||||||
|
|
|
||||||
BIN
cmd/devp2p/internal/ethtest/testdata/fullchain.rlp.gz
vendored
Normal file
BIN
cmd/devp2p/internal/ethtest/testdata/fullchain.rlp.gz
vendored
Normal file
Binary file not shown.
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"chainId": 1,
|
"chainId": 19763,
|
||||||
"homesteadBlock": 0,
|
"homesteadBlock": 0,
|
||||||
"eip150Block": 0,
|
"eip150Block": 0,
|
||||||
"eip155Block": 0,
|
"eip155Block": 0,
|
||||||
|
|
@ -11,16 +11,16 @@
|
||||||
"nonce": "0xdeadbeefdeadbeef",
|
"nonce": "0xdeadbeefdeadbeef",
|
||||||
"timestamp": "0x0",
|
"timestamp": "0x0",
|
||||||
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
"gasLimit": "0x8000000",
|
"gasLimit": "0x80000000",
|
||||||
"difficulty": "0x10",
|
"difficulty": "0x20000",
|
||||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||||
"alloc": {
|
"alloc": {
|
||||||
"71562b71999873db5b286df957af199ec94617f7": {
|
"71562b71999873db5b286df957af199ec94617f7": {
|
||||||
"balance": "0xf4240"
|
"balance": "0xffffffff"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"number": "0x0",
|
"number": "0x0",
|
||||||
"gasUsed": "0x0",
|
"gasUsed": "0x0",
|
||||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
cmd/devp2p/internal/ethtest/testdata/halfchain.rlp.gz
vendored
Normal file
BIN
cmd/devp2p/internal/ethtest/testdata/halfchain.rlp.gz
vendored
Normal file
Binary file not shown.
|
|
@ -42,10 +42,14 @@ type Error struct {
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Error) Unwrap() error { return e.err }
|
func (e *Error) Unwrap() error { return e.err }
|
||||||
func (e *Error) Error() string { return e.err.Error() }
|
func (e *Error) Error() string { return e.err.Error() }
|
||||||
func (e *Error) Code() int { return -1 }
|
func (e *Error) Code() int { return -1 }
|
||||||
func (e *Error) GoString() string { return e.Error() }
|
func (e *Error) String() string { return e.Error() }
|
||||||
|
|
||||||
|
func errorf(format string, args ...interface{}) *Error {
|
||||||
|
return &Error{fmt.Errorf(format, args...)}
|
||||||
|
}
|
||||||
|
|
||||||
// Hello is the RLP structure of the protocol handshake.
|
// Hello is the RLP structure of the protocol handshake.
|
||||||
type Hello struct {
|
type Hello struct {
|
||||||
|
|
@ -174,7 +178,7 @@ type Conn struct {
|
||||||
func (c *Conn) Read() Message {
|
func (c *Conn) Read() Message {
|
||||||
code, rawData, _, err := c.Conn.Read()
|
code, rawData, _, err := c.Conn.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Error{fmt.Errorf("could not read from connection: %v", err)}
|
return errorf("could not read from connection: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg Message
|
var msg Message
|
||||||
|
|
@ -202,20 +206,22 @@ func (c *Conn) Read() Message {
|
||||||
case (NewBlockHashes{}).Code():
|
case (NewBlockHashes{}).Code():
|
||||||
msg = new(NewBlockHashes)
|
msg = new(NewBlockHashes)
|
||||||
default:
|
default:
|
||||||
return &Error{fmt.Errorf("invalid message code: %d", code)}
|
return errorf("invalid message code: %d", code)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rlp.DecodeBytes(rawData, msg); err != nil {
|
if err := rlp.DecodeBytes(rawData, msg); err != nil {
|
||||||
return &Error{fmt.Errorf("could not rlp decode message: %v", err)}
|
return errorf("could not rlp decode message: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadAndServe serves GetBlockHeaders requests while waiting
|
// ReadAndServe serves GetBlockHeaders requests while waiting
|
||||||
// on another message from the node.
|
// on another message from the node.
|
||||||
func (c *Conn) ReadAndServe(chain *Chain) Message {
|
func (c *Conn) ReadAndServe(chain *Chain, timeout time.Duration) Message {
|
||||||
for {
|
start := time.Now()
|
||||||
|
for time.Since(start) < timeout {
|
||||||
|
timeout := time.Now().Add(10 * time.Second)
|
||||||
|
c.SetReadDeadline(timeout)
|
||||||
switch msg := c.Read().(type) {
|
switch msg := c.Read().(type) {
|
||||||
case *Ping:
|
case *Ping:
|
||||||
c.Write(&Pong{})
|
c.Write(&Pong{})
|
||||||
|
|
@ -223,16 +229,17 @@ func (c *Conn) ReadAndServe(chain *Chain) Message {
|
||||||
req := *msg
|
req := *msg
|
||||||
headers, err := chain.GetHeaders(req)
|
headers, err := chain.GetHeaders(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Error{fmt.Errorf("could not get headers for inbound header request: %v", err)}
|
return errorf("could not get headers for inbound header request: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Write(headers); err != nil {
|
if err := c.Write(headers); err != nil {
|
||||||
return &Error{fmt.Errorf("could not write to connection: %v", err)}
|
return errorf("could not write to connection: %v", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return errorf("no message received within %v", timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Write(msg Message) error {
|
func (c *Conn) Write(msg Message) error {
|
||||||
|
|
@ -242,12 +249,14 @@ func (c *Conn) Write(msg Message) error {
|
||||||
}
|
}
|
||||||
_, err = c.Conn.Write(uint64(msg.Code()), payload)
|
_, err = c.Conn.Write(uint64(msg.Code()), payload)
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handshake checks to make sure a `HELLO` is received.
|
// handshake checks to make sure a `HELLO` is received.
|
||||||
func (c *Conn) handshake(t *utesting.T) Message {
|
func (c *Conn) handshake(t *utesting.T) Message {
|
||||||
// write protoHandshake to client
|
defer c.SetDeadline(time.Time{})
|
||||||
|
c.SetDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
|
||||||
|
// write hello to client
|
||||||
pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:]
|
pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:]
|
||||||
ourHandshake := &Hello{
|
ourHandshake := &Hello{
|
||||||
Version: 5,
|
Version: 5,
|
||||||
|
|
@ -260,14 +269,13 @@ func (c *Conn) handshake(t *utesting.T) Message {
|
||||||
if err := c.Write(ourHandshake); err != nil {
|
if err := c.Write(ourHandshake); err != nil {
|
||||||
t.Fatalf("could not write to connection: %v", err)
|
t.Fatalf("could not write to connection: %v", err)
|
||||||
}
|
}
|
||||||
// read protoHandshake from client
|
// read hello from client
|
||||||
switch msg := c.Read().(type) {
|
switch msg := c.Read().(type) {
|
||||||
case *Hello:
|
case *Hello:
|
||||||
// set snappy if version is at least 5
|
// set snappy if version is at least 5
|
||||||
if msg.Version >= 5 {
|
if msg.Version >= 5 {
|
||||||
c.SetSnappy(true)
|
c.SetSnappy(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.negotiateEthProtocol(msg.Caps)
|
c.negotiateEthProtocol(msg.Caps)
|
||||||
if c.ethProtocolVersion == 0 {
|
if c.ethProtocolVersion == 0 {
|
||||||
t.Fatalf("unexpected eth protocol version")
|
t.Fatalf("unexpected eth protocol version")
|
||||||
|
|
@ -297,15 +305,17 @@ func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) {
|
||||||
// statusExchange performs a `Status` message exchange with the given
|
// statusExchange performs a `Status` message exchange with the given
|
||||||
// node.
|
// node.
|
||||||
func (c *Conn) statusExchange(t *utesting.T, chain *Chain) Message {
|
func (c *Conn) statusExchange(t *utesting.T, chain *Chain) Message {
|
||||||
|
defer c.SetDeadline(time.Time{})
|
||||||
|
c.SetDeadline(time.Now().Add(20 * time.Second))
|
||||||
|
|
||||||
// read status message from client
|
// read status message from client
|
||||||
var message Message
|
var message Message
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
for {
|
for {
|
||||||
switch msg := c.Read().(type) {
|
switch msg := c.Read().(type) {
|
||||||
case *Status:
|
case *Status:
|
||||||
if msg.Head != chain.blocks[chain.Len()-1].Hash() {
|
if msg.Head != chain.blocks[chain.Len()-1].Hash() {
|
||||||
t.Fatalf("wrong head in status: %v", msg.Head)
|
t.Fatalf("wrong head block in status: %s", msg.Head.String())
|
||||||
}
|
}
|
||||||
if msg.TD.Cmp(chain.TD(chain.Len())) != 0 {
|
if msg.TD.Cmp(chain.TD(chain.Len())) != 0 {
|
||||||
t.Fatalf("wrong TD in status: %v", msg.TD)
|
t.Fatalf("wrong TD in status: %v", msg.TD)
|
||||||
|
|
@ -321,7 +331,7 @@ loop:
|
||||||
c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error
|
c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error
|
||||||
// (PINGs should not be a response upon fresh connection)
|
// (PINGs should not be a response upon fresh connection)
|
||||||
default:
|
default:
|
||||||
t.Fatalf("bad status message: %#v", msg)
|
t.Fatalf("bad status message: %s", pretty.Sdump(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// make sure eth protocol version is set for negotiation
|
// make sure eth protocol version is set for negotiation
|
||||||
|
|
@ -331,7 +341,7 @@ loop:
|
||||||
// write status message to client
|
// write status message to client
|
||||||
status := Status{
|
status := Status{
|
||||||
ProtocolVersion: uint32(c.ethProtocolVersion),
|
ProtocolVersion: uint32(c.ethProtocolVersion),
|
||||||
NetworkID: 1,
|
NetworkID: chain.chainConfig.ChainID.Uint64(),
|
||||||
TD: chain.TD(chain.Len()),
|
TD: chain.TD(chain.Len()),
|
||||||
Head: chain.blocks[chain.Len()-1].Hash(),
|
Head: chain.blocks[chain.Len()-1].Hash(),
|
||||||
Genesis: chain.blocks[0].Hash(),
|
Genesis: chain.blocks[0].Hash(),
|
||||||
|
|
@ -347,12 +357,15 @@ loop:
|
||||||
// waitForBlock waits for confirmation from the client that it has
|
// waitForBlock waits for confirmation from the client that it has
|
||||||
// imported the given block.
|
// imported the given block.
|
||||||
func (c *Conn) waitForBlock(block *types.Block) error {
|
func (c *Conn) waitForBlock(block *types.Block) error {
|
||||||
|
defer c.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
|
timeout := time.Now().Add(20 * time.Second)
|
||||||
|
c.SetReadDeadline(timeout)
|
||||||
for {
|
for {
|
||||||
req := &GetBlockHeaders{Origin: hashOrNumber{Hash: block.Hash()}, Amount: 1}
|
req := &GetBlockHeaders{Origin: hashOrNumber{Hash: block.Hash()}, Amount: 1}
|
||||||
if err := c.Write(req); err != nil {
|
if err := c.Write(req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg := c.Read().(type) {
|
switch msg := c.Read().(type) {
|
||||||
case *BlockHeaders:
|
case *BlockHeaders:
|
||||||
if len(*msg) > 0 {
|
if len(*msg) > 0 {
|
||||||
|
|
@ -360,7 +373,7 @@ func (c *Conn) waitForBlock(block *types.Block) error {
|
||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid message: %v", msg)
|
return fmt.Errorf("invalid message: %s", pretty.Sdump(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/ethtest"
|
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/ethtest"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/rlpx"
|
"github.com/ethereum/go-ethereum/p2p/rlpx"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
|
@ -47,9 +45,12 @@ var (
|
||||||
rlpxEthTestCommand = cli.Command{
|
rlpxEthTestCommand = cli.Command{
|
||||||
Name: "eth-test",
|
Name: "eth-test",
|
||||||
Usage: "Runs tests against a node",
|
Usage: "Runs tests against a node",
|
||||||
ArgsUsage: "<node> <path_to_chain.rlp_file>",
|
ArgsUsage: "<node> <chain.rlp> <genesis.json>",
|
||||||
Action: rlpxEthTest,
|
Action: rlpxEthTest,
|
||||||
Flags: []cli.Flag{testPatternFlag},
|
Flags: []cli.Flag{
|
||||||
|
testPatternFlag,
|
||||||
|
testTAPFlag,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -88,22 +89,11 @@ func rlpxPing(ctx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rlpxEthTest runs the eth protocol test suite.
|
||||||
func rlpxEthTest(ctx *cli.Context) error {
|
func rlpxEthTest(ctx *cli.Context) error {
|
||||||
if ctx.NArg() < 3 {
|
if ctx.NArg() < 3 {
|
||||||
exit("missing path to chain.rlp as command-line argument")
|
exit("missing path to chain.rlp as command-line argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
suite := ethtest.NewSuite(getNodeArg(ctx), ctx.Args()[1], ctx.Args()[2])
|
suite := ethtest.NewSuite(getNodeArg(ctx), ctx.Args()[1], ctx.Args()[2])
|
||||||
|
return runTests(ctx, suite.AllTests())
|
||||||
// Filter and run test cases.
|
|
||||||
tests := suite.AllTests()
|
|
||||||
if ctx.IsSet(testPatternFlag.Name) {
|
|
||||||
tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name))
|
|
||||||
}
|
|
||||||
results := utesting.RunTests(tests, os.Stdout)
|
|
||||||
if fails := utesting.CountFailures(results); fails > 0 {
|
|
||||||
return fmt.Errorf("%v of %v tests passed.", len(tests)-fails, len(tests))
|
|
||||||
}
|
|
||||||
fmt.Printf("all tests passed\n")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
69
cmd/devp2p/runtest.go
Normal file
69
cmd/devp2p/runtest.go
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright 2020 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testPatternFlag = cli.StringFlag{
|
||||||
|
Name: "run",
|
||||||
|
Usage: "Pattern of test suite(s) to run",
|
||||||
|
}
|
||||||
|
testTAPFlag = cli.BoolFlag{
|
||||||
|
Name: "tap",
|
||||||
|
Usage: "Output TAP",
|
||||||
|
}
|
||||||
|
// These two are specific to the discovery tests.
|
||||||
|
testListen1Flag = cli.StringFlag{
|
||||||
|
Name: "listen1",
|
||||||
|
Usage: "IP address of the first tester",
|
||||||
|
Value: v4test.Listen1,
|
||||||
|
}
|
||||||
|
testListen2Flag = cli.StringFlag{
|
||||||
|
Name: "listen2",
|
||||||
|
Usage: "IP address of the second tester",
|
||||||
|
Value: v4test.Listen2,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func runTests(ctx *cli.Context, tests []utesting.Test) error {
|
||||||
|
// Filter test cases.
|
||||||
|
if ctx.IsSet(testPatternFlag.Name) {
|
||||||
|
tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name))
|
||||||
|
}
|
||||||
|
// Disable logging unless explicitly enabled.
|
||||||
|
if !ctx.GlobalIsSet("verbosity") && !ctx.GlobalIsSet("vmodule") {
|
||||||
|
log.Root().SetHandler(log.DiscardHandler())
|
||||||
|
}
|
||||||
|
// Run the tests.
|
||||||
|
var run = utesting.RunTests
|
||||||
|
if ctx.Bool(testTAPFlag.Name) {
|
||||||
|
run = utesting.RunTAP
|
||||||
|
}
|
||||||
|
results := run(tests, os.Stdout)
|
||||||
|
if utesting.CountFailures(results) > 0 {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -147,6 +147,16 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||||
vmContext.Origin = msg.From()
|
vmContext.Origin = msg.From()
|
||||||
|
|
||||||
evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig)
|
evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig)
|
||||||
|
if chainConfig.IsYoloV2(vmContext.BlockNumber) {
|
||||||
|
statedb.AddAddressToAccessList(msg.From())
|
||||||
|
if dst := msg.To(); dst != nil {
|
||||||
|
statedb.AddAddressToAccessList(*dst)
|
||||||
|
// If it's a create-tx, the destination will be added inside evm.create
|
||||||
|
}
|
||||||
|
for _, addr := range evm.ActivePrecompiles() {
|
||||||
|
statedb.AddAddressToAccessList(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
snapshot := statedb.Snapshot()
|
snapshot := statedb.Snapshot()
|
||||||
// (ret []byte, usedGas uint64, failed bool, err error)
|
// (ret []byte, usedGas uint64, failed bool, err error)
|
||||||
msgResult, err := core.ApplyMessage(evm, msg, gaspool)
|
msgResult, err := core.ApplyMessage(evm, msg, gaspool)
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ The export-preimages command export hash preimages to an RLP encoded stream`,
|
||||||
utils.RinkebyFlag,
|
utils.RinkebyFlag,
|
||||||
utils.TxLookupLimitFlag,
|
utils.TxLookupLimitFlag,
|
||||||
utils.GoerliFlag,
|
utils.GoerliFlag,
|
||||||
utils.YoloV1Flag,
|
utils.YoloV2Flag,
|
||||||
utils.LegacyTestnetFlag,
|
utils.LegacyTestnetFlag,
|
||||||
},
|
},
|
||||||
Category: "BLOCKCHAIN COMMANDS",
|
Category: "BLOCKCHAIN COMMANDS",
|
||||||
|
|
@ -217,7 +217,7 @@ Use "ethereum dump 0" to dump the genesis block.`,
|
||||||
utils.RopstenFlag,
|
utils.RopstenFlag,
|
||||||
utils.RinkebyFlag,
|
utils.RinkebyFlag,
|
||||||
utils.GoerliFlag,
|
utils.GoerliFlag,
|
||||||
utils.YoloV1Flag,
|
utils.YoloV2Flag,
|
||||||
utils.LegacyTestnetFlag,
|
utils.LegacyTestnetFlag,
|
||||||
utils.SyncModeFlag,
|
utils.SyncModeFlag,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -136,8 +136,8 @@ func remoteConsole(ctx *cli.Context) error {
|
||||||
path = filepath.Join(path, "rinkeby")
|
path = filepath.Join(path, "rinkeby")
|
||||||
} else if ctx.GlobalBool(utils.GoerliFlag.Name) {
|
} else if ctx.GlobalBool(utils.GoerliFlag.Name) {
|
||||||
path = filepath.Join(path, "goerli")
|
path = filepath.Join(path, "goerli")
|
||||||
} else if ctx.GlobalBool(utils.YoloV1Flag.Name) {
|
} else if ctx.GlobalBool(utils.YoloV2Flag.Name) {
|
||||||
path = filepath.Join(path, "yolo-v1")
|
path = filepath.Join(path, "yolo-v2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
endpoint = fmt.Sprintf("%s/geth.ipc", path)
|
endpoint = fmt.Sprintf("%s/geth.ipc", path)
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ at block: 0 ({{niltime}})
|
||||||
datadir: {{.Datadir}}
|
datadir: {{.Datadir}}
|
||||||
modules: {{apis}}
|
modules: {{apis}}
|
||||||
|
|
||||||
|
To exit, press ctrl-d
|
||||||
> {{.InputLine "exit"}}
|
> {{.InputLine "exit"}}
|
||||||
`)
|
`)
|
||||||
geth.ExpectExit()
|
geth.ExpectExit()
|
||||||
|
|
@ -159,6 +160,7 @@ at block: 0 ({{niltime}}){{if ipc}}
|
||||||
datadir: {{datadir}}{{end}}
|
datadir: {{datadir}}{{end}}
|
||||||
modules: {{apis}}
|
modules: {{apis}}
|
||||||
|
|
||||||
|
To exit, press ctrl-d
|
||||||
> {{.InputLine "exit" }}
|
> {{.InputLine "exit" }}
|
||||||
`)
|
`)
|
||||||
attach.ExpectExit()
|
attach.ExpectExit()
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ var (
|
||||||
utils.RopstenFlag,
|
utils.RopstenFlag,
|
||||||
utils.RinkebyFlag,
|
utils.RinkebyFlag,
|
||||||
utils.GoerliFlag,
|
utils.GoerliFlag,
|
||||||
utils.YoloV1Flag,
|
utils.YoloV2Flag,
|
||||||
utils.VMEnableDebugFlag,
|
utils.VMEnableDebugFlag,
|
||||||
utils.NetworkIdFlag,
|
utils.NetworkIdFlag,
|
||||||
utils.EthStatsURLFlag,
|
utils.EthStatsURLFlag,
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
|
||||||
utils.NetworkIdFlag,
|
utils.NetworkIdFlag,
|
||||||
utils.GoerliFlag,
|
utils.GoerliFlag,
|
||||||
utils.RinkebyFlag,
|
utils.RinkebyFlag,
|
||||||
utils.YoloV1Flag,
|
utils.YoloV2Flag,
|
||||||
utils.RopstenFlag,
|
utils.RopstenFlag,
|
||||||
utils.SyncModeFlag,
|
utils.SyncModeFlag,
|
||||||
utils.ExitWhenSyncedFlag,
|
utils.ExitWhenSyncedFlag,
|
||||||
|
|
|
||||||
|
|
@ -236,8 +236,8 @@ func (w *wizard) manageGenesis() {
|
||||||
w.conf.Genesis.Config.IstanbulBlock = w.readDefaultBigInt(w.conf.Genesis.Config.IstanbulBlock)
|
w.conf.Genesis.Config.IstanbulBlock = w.readDefaultBigInt(w.conf.Genesis.Config.IstanbulBlock)
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Printf("Which block should YOLOv1 come into effect? (default = %v)\n", w.conf.Genesis.Config.YoloV1Block)
|
fmt.Printf("Which block should YOLOv2 come into effect? (default = %v)\n", w.conf.Genesis.Config.YoloV2Block)
|
||||||
w.conf.Genesis.Config.YoloV1Block = w.readDefaultBigInt(w.conf.Genesis.Config.YoloV1Block)
|
w.conf.Genesis.Config.YoloV2Block = w.readDefaultBigInt(w.conf.Genesis.Config.YoloV2Block)
|
||||||
|
|
||||||
out, _ := json.MarshalIndent(w.conf.Genesis.Config, "", " ")
|
out, _ := json.MarshalIndent(w.conf.Genesis.Config, "", " ")
|
||||||
fmt.Printf("Chain configuration updated:\n\n%s\n", out)
|
fmt.Printf("Chain configuration updated:\n\n%s\n", out)
|
||||||
|
|
|
||||||
|
|
@ -135,9 +135,9 @@ var (
|
||||||
Name: "goerli",
|
Name: "goerli",
|
||||||
Usage: "Görli network: pre-configured proof-of-authority test network",
|
Usage: "Görli network: pre-configured proof-of-authority test network",
|
||||||
}
|
}
|
||||||
YoloV1Flag = cli.BoolFlag{
|
YoloV2Flag = cli.BoolFlag{
|
||||||
Name: "yolov1",
|
Name: "yolov2",
|
||||||
Usage: "YOLOv1 network: pre-configured proof-of-authority shortlived test network.",
|
Usage: "YOLOv2 network: pre-configured proof-of-authority shortlived test network.",
|
||||||
}
|
}
|
||||||
RinkebyFlag = cli.BoolFlag{
|
RinkebyFlag = cli.BoolFlag{
|
||||||
Name: "rinkeby",
|
Name: "rinkeby",
|
||||||
|
|
@ -744,8 +744,8 @@ func MakeDataDir(ctx *cli.Context) string {
|
||||||
if ctx.GlobalBool(GoerliFlag.Name) {
|
if ctx.GlobalBool(GoerliFlag.Name) {
|
||||||
return filepath.Join(path, "goerli")
|
return filepath.Join(path, "goerli")
|
||||||
}
|
}
|
||||||
if ctx.GlobalBool(YoloV1Flag.Name) {
|
if ctx.GlobalBool(YoloV2Flag.Name) {
|
||||||
return filepath.Join(path, "yolo-v1")
|
return filepath.Join(path, "yolo-v2")
|
||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
@ -803,8 +803,8 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
|
||||||
urls = params.RinkebyBootnodes
|
urls = params.RinkebyBootnodes
|
||||||
case ctx.GlobalBool(GoerliFlag.Name):
|
case ctx.GlobalBool(GoerliFlag.Name):
|
||||||
urls = params.GoerliBootnodes
|
urls = params.GoerliBootnodes
|
||||||
case ctx.GlobalBool(YoloV1Flag.Name):
|
case ctx.GlobalBool(YoloV2Flag.Name):
|
||||||
urls = params.YoloV1Bootnodes
|
urls = params.YoloV2Bootnodes
|
||||||
case cfg.BootstrapNodes != nil:
|
case cfg.BootstrapNodes != nil:
|
||||||
return // already set, don't apply defaults.
|
return // already set, don't apply defaults.
|
||||||
}
|
}
|
||||||
|
|
@ -839,8 +839,8 @@ func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) {
|
||||||
urls = params.RinkebyBootnodes
|
urls = params.RinkebyBootnodes
|
||||||
case ctx.GlobalBool(GoerliFlag.Name):
|
case ctx.GlobalBool(GoerliFlag.Name):
|
||||||
urls = params.GoerliBootnodes
|
urls = params.GoerliBootnodes
|
||||||
case ctx.GlobalBool(YoloV1Flag.Name):
|
case ctx.GlobalBool(YoloV2Flag.Name):
|
||||||
urls = params.YoloV1Bootnodes
|
urls = params.YoloV2Bootnodes
|
||||||
case cfg.BootstrapNodesV5 != nil:
|
case cfg.BootstrapNodesV5 != nil:
|
||||||
return // already set, don't apply defaults.
|
return // already set, don't apply defaults.
|
||||||
}
|
}
|
||||||
|
|
@ -1269,8 +1269,8 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) {
|
||||||
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby")
|
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby")
|
||||||
case ctx.GlobalBool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir():
|
case ctx.GlobalBool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir():
|
||||||
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "goerli")
|
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "goerli")
|
||||||
case ctx.GlobalBool(YoloV1Flag.Name) && cfg.DataDir == node.DefaultDataDir():
|
case ctx.GlobalBool(YoloV2Flag.Name) && cfg.DataDir == node.DefaultDataDir():
|
||||||
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "yolo-v1")
|
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "yolo-v2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1483,7 +1483,7 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node) {
|
||||||
// SetEthConfig applies eth-related command line flags to the config.
|
// SetEthConfig applies eth-related command line flags to the config.
|
||||||
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||||
// Avoid conflicting network flags
|
// Avoid conflicting network flags
|
||||||
CheckExclusive(ctx, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV1Flag)
|
CheckExclusive(ctx, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV2Flag)
|
||||||
CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, SyncModeFlag, "light")
|
CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, SyncModeFlag, "light")
|
||||||
CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
|
CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
|
||||||
CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag)
|
CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag)
|
||||||
|
|
@ -1603,11 +1603,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||||
}
|
}
|
||||||
cfg.Genesis = core.DefaultGoerliGenesisBlock()
|
cfg.Genesis = core.DefaultGoerliGenesisBlock()
|
||||||
SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash)
|
SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash)
|
||||||
case ctx.GlobalBool(YoloV1Flag.Name):
|
case ctx.GlobalBool(YoloV2Flag.Name):
|
||||||
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
|
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
|
||||||
cfg.NetworkId = 133519467574833 // "yolov1"
|
cfg.NetworkId = 133519467574834 // "yolov2"
|
||||||
}
|
}
|
||||||
cfg.Genesis = core.DefaultYoloV1GenesisBlock()
|
cfg.Genesis = core.DefaultYoloV2GenesisBlock()
|
||||||
case ctx.GlobalBool(DeveloperFlag.Name):
|
case ctx.GlobalBool(DeveloperFlag.Name):
|
||||||
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
|
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
|
||||||
cfg.NetworkId = 1337
|
cfg.NetworkId = 1337
|
||||||
|
|
@ -1791,8 +1791,8 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis {
|
||||||
genesis = core.DefaultRinkebyGenesisBlock()
|
genesis = core.DefaultRinkebyGenesisBlock()
|
||||||
case ctx.GlobalBool(GoerliFlag.Name):
|
case ctx.GlobalBool(GoerliFlag.Name):
|
||||||
genesis = core.DefaultGoerliGenesisBlock()
|
genesis = core.DefaultGoerliGenesisBlock()
|
||||||
case ctx.GlobalBool(YoloV1Flag.Name):
|
case ctx.GlobalBool(YoloV2Flag.Name):
|
||||||
genesis = core.DefaultYoloV1GenesisBlock()
|
genesis = core.DefaultYoloV2GenesisBlock()
|
||||||
case ctx.GlobalBool(DeveloperFlag.Name):
|
case ctx.GlobalBool(DeveloperFlag.Name):
|
||||||
Fatalf("Developer chains are ephemeral")
|
Fatalf("Developer chains are ephemeral")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,28 +17,9 @@
|
||||||
// Package common contains various helper functions.
|
// Package common contains various helper functions.
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import "encoding/hex"
|
import (
|
||||||
|
"encoding/hex"
|
||||||
// ToHex returns the hex representation of b, prefixed with '0x'.
|
)
|
||||||
// For empty slices, the return value is "0x0".
|
|
||||||
//
|
|
||||||
// Deprecated: use hexutil.Encode instead.
|
|
||||||
func ToHex(b []byte) string {
|
|
||||||
hex := Bytes2Hex(b)
|
|
||||||
if len(hex) == 0 {
|
|
||||||
hex = "0"
|
|
||||||
}
|
|
||||||
return "0x" + hex
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToHexArray creates a array of hex-string based on []byte
|
|
||||||
func ToHexArray(b [][]byte) []string {
|
|
||||||
r := make([]string, len(b))
|
|
||||||
for i := range b {
|
|
||||||
r[i] = ToHex(b[i])
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromHex returns the bytes represented by the hexadecimal string s.
|
// FromHex returns the bytes represented by the hexadecimal string s.
|
||||||
// s may be prefixed with "0x".
|
// s may be prefixed with "0x".
|
||||||
|
|
|
||||||
|
|
@ -295,7 +295,7 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) {
|
||||||
var pend sync.WaitGroup
|
var pend sync.WaitGroup
|
||||||
pend.Add(threads)
|
pend.Add(threads)
|
||||||
|
|
||||||
var progress uint32
|
var progress uint64
|
||||||
for i := 0; i < threads; i++ {
|
for i := 0; i < threads; i++ {
|
||||||
go func(id int) {
|
go func(id int) {
|
||||||
defer pend.Done()
|
defer pend.Done()
|
||||||
|
|
@ -304,23 +304,23 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) {
|
||||||
keccak512 := makeHasher(sha3.NewLegacyKeccak512())
|
keccak512 := makeHasher(sha3.NewLegacyKeccak512())
|
||||||
|
|
||||||
// Calculate the data segment this thread should generate
|
// Calculate the data segment this thread should generate
|
||||||
batch := uint32((size + hashBytes*uint64(threads) - 1) / (hashBytes * uint64(threads)))
|
batch := (size + hashBytes*uint64(threads) - 1) / (hashBytes * uint64(threads))
|
||||||
first := uint32(id) * batch
|
first := uint64(id) * batch
|
||||||
limit := first + batch
|
limit := first + batch
|
||||||
if limit > uint32(size/hashBytes) {
|
if limit > size/hashBytes {
|
||||||
limit = uint32(size / hashBytes)
|
limit = size / hashBytes
|
||||||
}
|
}
|
||||||
// Calculate the dataset segment
|
// Calculate the dataset segment
|
||||||
percent := uint32(size / hashBytes / 100)
|
percent := size / hashBytes / 100
|
||||||
for index := first; index < limit; index++ {
|
for index := first; index < limit; index++ {
|
||||||
item := generateDatasetItem(cache, index, keccak512)
|
item := generateDatasetItem(cache, uint32(index), keccak512)
|
||||||
if swapped {
|
if swapped {
|
||||||
swap(item)
|
swap(item)
|
||||||
}
|
}
|
||||||
copy(dataset[index*hashBytes:], item)
|
copy(dataset[index*hashBytes:], item)
|
||||||
|
|
||||||
if status := atomic.AddUint32(&progress, 1); status%percent == 0 {
|
if status := atomic.AddUint64(&progress, 1); status%percent == 0 {
|
||||||
logger.Info("Generating DAG in progress", "percentage", uint64(status*100)/(size/hashBytes), "elapsed", common.PrettyDuration(time.Since(start)))
|
logger.Info("Generating DAG in progress", "percentage", (status*100)/(size/hashBytes), "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(i)
|
}(i)
|
||||||
|
|
|
||||||
|
|
@ -324,6 +324,7 @@ func (c *Console) Welcome() {
|
||||||
sort.Strings(modules)
|
sort.Strings(modules)
|
||||||
message += " modules: " + strings.Join(modules, " ") + "\n"
|
message += " modules: " + strings.Join(modules, " ") + "\n"
|
||||||
}
|
}
|
||||||
|
message += "\nTo exit, press ctrl-d"
|
||||||
fmt.Fprintln(c.printer, message)
|
fmt.Fprintln(c.printer, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -372,7 +373,7 @@ func (c *Console) Interactive() {
|
||||||
return
|
return
|
||||||
|
|
||||||
case err := <-inputErr:
|
case err := <-inputErr:
|
||||||
if err == liner.ErrPromptAborted && indents > 0 {
|
if err == liner.ErrPromptAborted {
|
||||||
// When prompting for multi-line input, the first Ctrl-C resets
|
// When prompting for multi-line input, the first Ctrl-C resets
|
||||||
// the multi-line state.
|
// the multi-line state.
|
||||||
prompt, indents, input = c.prompt, 0, ""
|
prompt, indents, input = c.prompt, 0, ""
|
||||||
|
|
|
||||||
|
|
@ -207,9 +207,10 @@ type BlockChain struct {
|
||||||
processor Processor // Block transaction processor interface
|
processor Processor // Block transaction processor interface
|
||||||
vmConfig vm.Config
|
vmConfig vm.Config
|
||||||
|
|
||||||
badBlocks *lru.Cache // Bad block cache
|
badBlocks *lru.Cache // Bad block cache
|
||||||
shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block.
|
shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block.
|
||||||
terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion.
|
terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion.
|
||||||
|
writeLegacyJournal bool // Testing flag used to flush the snapshot journal in legacy format.
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBlockChain returns a fully initialised block chain using information
|
// NewBlockChain returns a fully initialised block chain using information
|
||||||
|
|
@ -281,9 +282,29 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||||
// Make sure the state associated with the block is available
|
// Make sure the state associated with the block is available
|
||||||
head := bc.CurrentBlock()
|
head := bc.CurrentBlock()
|
||||||
if _, err := state.New(head.Root(), bc.stateCache, bc.snaps); err != nil {
|
if _, err := state.New(head.Root(), bc.stateCache, bc.snaps); err != nil {
|
||||||
log.Warn("Head state missing, repairing", "number", head.Number(), "hash", head.Hash())
|
// Head state is missing, before the state recovery, find out the
|
||||||
if err := bc.SetHead(head.NumberU64()); err != nil {
|
// disk layer point of snapshot(if it's enabled). Make sure the
|
||||||
return nil, err
|
// rewound point is lower than disk layer.
|
||||||
|
var diskRoot common.Hash
|
||||||
|
if bc.cacheConfig.SnapshotLimit > 0 {
|
||||||
|
diskRoot = rawdb.ReadSnapshotRoot(bc.db)
|
||||||
|
}
|
||||||
|
if diskRoot != (common.Hash{}) {
|
||||||
|
log.Warn("Head state missing, repairing", "number", head.Number(), "hash", head.Hash(), "snaproot", diskRoot)
|
||||||
|
|
||||||
|
snapDisk, err := bc.SetHeadBeyondRoot(head.NumberU64(), diskRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Chain rewound, persist old snapshot number to indicate recovery procedure
|
||||||
|
if snapDisk != 0 {
|
||||||
|
rawdb.WriteSnapshotRecoveryNumber(bc.db, snapDisk)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warn("Head state missing, repairing", "number", head.Number(), "hash", head.Hash())
|
||||||
|
if err := bc.SetHead(head.NumberU64()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Ensure that a previous crash in SetHead doesn't leave extra ancients
|
// Ensure that a previous crash in SetHead doesn't leave extra ancients
|
||||||
|
|
@ -339,12 +360,25 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||||
}
|
}
|
||||||
// Load any existing snapshot, regenerating it if loading failed
|
// Load any existing snapshot, regenerating it if loading failed
|
||||||
if bc.cacheConfig.SnapshotLimit > 0 {
|
if bc.cacheConfig.SnapshotLimit > 0 {
|
||||||
bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, bc.CurrentBlock().Root(), !bc.cacheConfig.SnapshotWait)
|
// If the chain was rewound past the snapshot persistent layer (causing
|
||||||
|
// a recovery block number to be persisted to disk), check if we're still
|
||||||
|
// in recovery mode and in that case, don't invalidate the snapshot on a
|
||||||
|
// head mismatch.
|
||||||
|
var recover bool
|
||||||
|
|
||||||
|
head := bc.CurrentBlock()
|
||||||
|
if layer := rawdb.ReadSnapshotRecoveryNumber(bc.db); layer != nil && *layer > head.NumberU64() {
|
||||||
|
log.Warn("Enabling snapshot recovery", "chainhead", head.NumberU64(), "diskbase", *layer)
|
||||||
|
recover = true
|
||||||
|
}
|
||||||
|
bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, head.Root(), !bc.cacheConfig.SnapshotWait, recover)
|
||||||
}
|
}
|
||||||
// Take ownership of this particular state
|
// Take ownership of this particular state
|
||||||
go bc.update()
|
go bc.update()
|
||||||
if txLookupLimit != nil {
|
if txLookupLimit != nil {
|
||||||
bc.txLookupLimit = *txLookupLimit
|
bc.txLookupLimit = *txLookupLimit
|
||||||
|
|
||||||
|
bc.wg.Add(1)
|
||||||
go bc.maintainTxIndex(txIndexBlock)
|
go bc.maintainTxIndex(txIndexBlock)
|
||||||
}
|
}
|
||||||
// If periodic cache journal is required, spin it up.
|
// If periodic cache journal is required, spin it up.
|
||||||
|
|
@ -442,9 +476,25 @@ func (bc *BlockChain) loadLastState() error {
|
||||||
// was fast synced or full synced and in which state, the method will try to
|
// was fast synced or full synced and in which state, the method will try to
|
||||||
// delete minimal data from disk whilst retaining chain consistency.
|
// delete minimal data from disk whilst retaining chain consistency.
|
||||||
func (bc *BlockChain) SetHead(head uint64) error {
|
func (bc *BlockChain) SetHead(head uint64) error {
|
||||||
|
_, err := bc.SetHeadBeyondRoot(head, common.Hash{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHeadBeyondRoot rewinds the local chain to a new head with the extra condition
|
||||||
|
// that the rewind must pass the specified state root. This method is meant to be
|
||||||
|
// used when rewiding with snapshots enabled to ensure that we go back further than
|
||||||
|
// persistent disk layer. Depending on whether the node was fast synced or full, and
|
||||||
|
// in which state, the method will try to delete minimal data from disk whilst
|
||||||
|
// retaining chain consistency.
|
||||||
|
//
|
||||||
|
// The method returns the block number where the requested root cap was found.
|
||||||
|
func (bc *BlockChain) SetHeadBeyondRoot(head uint64, root common.Hash) (uint64, error) {
|
||||||
bc.chainmu.Lock()
|
bc.chainmu.Lock()
|
||||||
defer bc.chainmu.Unlock()
|
defer bc.chainmu.Unlock()
|
||||||
|
|
||||||
|
// Track the block number of the requested root hash
|
||||||
|
var rootNumber uint64 // (no root == always 0)
|
||||||
|
|
||||||
// Retrieve the last pivot block to short circuit rollbacks beyond it and the
|
// Retrieve the last pivot block to short circuit rollbacks beyond it and the
|
||||||
// current freezer limit to start nuking id underflown
|
// current freezer limit to start nuking id underflown
|
||||||
pivot := rawdb.ReadLastPivotNumber(bc.db)
|
pivot := rawdb.ReadLastPivotNumber(bc.db)
|
||||||
|
|
@ -460,8 +510,16 @@ func (bc *BlockChain) SetHead(head uint64) error {
|
||||||
log.Error("Gap in the chain, rewinding to genesis", "number", header.Number, "hash", header.Hash())
|
log.Error("Gap in the chain, rewinding to genesis", "number", header.Number, "hash", header.Hash())
|
||||||
newHeadBlock = bc.genesisBlock
|
newHeadBlock = bc.genesisBlock
|
||||||
} else {
|
} else {
|
||||||
// Block exists, keep rewinding until we find one with state
|
// Block exists, keep rewinding until we find one with state,
|
||||||
|
// keeping rewinding until we exceed the optional threshold
|
||||||
|
// root hash
|
||||||
|
beyondRoot := (root == common.Hash{}) // Flag whether we're beyond the requested root (no root, always true)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
// If a root threshold was requested but not yet crossed, check
|
||||||
|
if root != (common.Hash{}) && !beyondRoot && newHeadBlock.Root() == root {
|
||||||
|
beyondRoot, rootNumber = true, newHeadBlock.NumberU64()
|
||||||
|
}
|
||||||
if _, err := state.New(newHeadBlock.Root(), bc.stateCache, bc.snaps); err != nil {
|
if _, err := state.New(newHeadBlock.Root(), bc.stateCache, bc.snaps); err != nil {
|
||||||
log.Trace("Block state missing, rewinding further", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash())
|
log.Trace("Block state missing, rewinding further", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash())
|
||||||
if pivot == nil || newHeadBlock.NumberU64() > *pivot {
|
if pivot == nil || newHeadBlock.NumberU64() > *pivot {
|
||||||
|
|
@ -472,8 +530,12 @@ func (bc *BlockChain) SetHead(head uint64) error {
|
||||||
newHeadBlock = bc.genesisBlock
|
newHeadBlock = bc.genesisBlock
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Debug("Rewound to block with state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash())
|
if beyondRoot || newHeadBlock.NumberU64() == 0 {
|
||||||
break
|
log.Debug("Rewound to block with state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Debug("Skipping block with threshold state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash(), "root", newHeadBlock.Root())
|
||||||
|
newHeadBlock = bc.GetBlock(newHeadBlock.ParentHash(), newHeadBlock.NumberU64()-1) // Keep rewinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rawdb.WriteHeadBlockHash(db, newHeadBlock.Hash())
|
rawdb.WriteHeadBlockHash(db, newHeadBlock.Hash())
|
||||||
|
|
@ -553,7 +615,7 @@ func (bc *BlockChain) SetHead(head uint64) error {
|
||||||
bc.txLookupCache.Purge()
|
bc.txLookupCache.Purge()
|
||||||
bc.futureBlocks.Purge()
|
bc.futureBlocks.Purge()
|
||||||
|
|
||||||
return bc.loadLastState()
|
return rootNumber, bc.loadLastState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FastSyncCommitHead sets the current head block to the one defined by the hash
|
// FastSyncCommitHead sets the current head block to the one defined by the hash
|
||||||
|
|
@ -938,8 +1000,14 @@ func (bc *BlockChain) Stop() {
|
||||||
var snapBase common.Hash
|
var snapBase common.Hash
|
||||||
if bc.snaps != nil {
|
if bc.snaps != nil {
|
||||||
var err error
|
var err error
|
||||||
if snapBase, err = bc.snaps.Journal(bc.CurrentBlock().Root()); err != nil {
|
if bc.writeLegacyJournal {
|
||||||
log.Error("Failed to journal state snapshot", "err", err)
|
if snapBase, err = bc.snaps.LegacyJournal(bc.CurrentBlock().Root()); err != nil {
|
||||||
|
log.Error("Failed to journal state snapshot", "err", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if snapBase, err = bc.snaps.Journal(bc.CurrentBlock().Root()); err != nil {
|
||||||
|
log.Error("Failed to journal state snapshot", "err", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Ensure the state of a recent block is also stored to disk before exiting.
|
// Ensure the state of a recent block is also stored to disk before exiting.
|
||||||
|
|
@ -2096,6 +2164,20 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
// mergeLogs returns a merged log slice with specified sort order.
|
||||||
|
mergeLogs = func(logs [][]*types.Log, reverse bool) []*types.Log {
|
||||||
|
var ret []*types.Log
|
||||||
|
if reverse {
|
||||||
|
for i := len(logs) - 1; i >= 0; i-- {
|
||||||
|
ret = append(ret, logs[i]...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := 0; i < len(logs); i++ {
|
||||||
|
ret = append(ret, logs[i]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
)
|
)
|
||||||
// Reduce the longer chain to the same number as the shorter one
|
// Reduce the longer chain to the same number as the shorter one
|
||||||
if oldBlock.NumberU64() > newBlock.NumberU64() {
|
if oldBlock.NumberU64() > newBlock.NumberU64() {
|
||||||
|
|
@ -2230,6 +2312,8 @@ func (bc *BlockChain) update() {
|
||||||
// sync, Geth will automatically construct the missing indices and delete
|
// sync, Geth will automatically construct the missing indices and delete
|
||||||
// the extra indices.
|
// the extra indices.
|
||||||
func (bc *BlockChain) maintainTxIndex(ancients uint64) {
|
func (bc *BlockChain) maintainTxIndex(ancients uint64) {
|
||||||
|
defer bc.wg.Done()
|
||||||
|
|
||||||
// Before starting the actual maintenance, we need to handle a special case,
|
// Before starting the actual maintenance, we need to handle a special case,
|
||||||
// where user might init Geth with an external ancient database. If so, we
|
// where user might init Geth with an external ancient database. If so, we
|
||||||
// need to reindex all necessary transactions before starting to process any
|
// need to reindex all necessary transactions before starting to process any
|
||||||
|
|
@ -2239,7 +2323,7 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) {
|
||||||
if bc.txLookupLimit != 0 && ancients > bc.txLookupLimit {
|
if bc.txLookupLimit != 0 && ancients > bc.txLookupLimit {
|
||||||
from = ancients - bc.txLookupLimit
|
from = ancients - bc.txLookupLimit
|
||||||
}
|
}
|
||||||
rawdb.IndexTransactions(bc.db, from, ancients)
|
rawdb.IndexTransactions(bc.db, from, ancients, bc.quit)
|
||||||
}
|
}
|
||||||
// indexBlocks reindexes or unindexes transactions depending on user configuration
|
// indexBlocks reindexes or unindexes transactions depending on user configuration
|
||||||
indexBlocks := func(tail *uint64, head uint64, done chan struct{}) {
|
indexBlocks := func(tail *uint64, head uint64, done chan struct{}) {
|
||||||
|
|
@ -2253,24 +2337,24 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) {
|
||||||
rawdb.WriteTxIndexTail(bc.db, 0)
|
rawdb.WriteTxIndexTail(bc.db, 0)
|
||||||
} else {
|
} else {
|
||||||
// Prune all stale tx indices and record the tx index tail
|
// Prune all stale tx indices and record the tx index tail
|
||||||
rawdb.UnindexTransactions(bc.db, 0, head-bc.txLookupLimit+1)
|
rawdb.UnindexTransactions(bc.db, 0, head-bc.txLookupLimit+1, bc.quit)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// If a previous indexing existed, make sure that we fill in any missing entries
|
// If a previous indexing existed, make sure that we fill in any missing entries
|
||||||
if bc.txLookupLimit == 0 || head < bc.txLookupLimit {
|
if bc.txLookupLimit == 0 || head < bc.txLookupLimit {
|
||||||
if *tail > 0 {
|
if *tail > 0 {
|
||||||
rawdb.IndexTransactions(bc.db, 0, *tail)
|
rawdb.IndexTransactions(bc.db, 0, *tail, bc.quit)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Update the transaction index to the new chain state
|
// Update the transaction index to the new chain state
|
||||||
if head-bc.txLookupLimit+1 < *tail {
|
if head-bc.txLookupLimit+1 < *tail {
|
||||||
// Reindex a part of missing indices and rewind index tail to HEAD-limit
|
// Reindex a part of missing indices and rewind index tail to HEAD-limit
|
||||||
rawdb.IndexTransactions(bc.db, head-bc.txLookupLimit+1, *tail)
|
rawdb.IndexTransactions(bc.db, head-bc.txLookupLimit+1, *tail, bc.quit)
|
||||||
} else {
|
} else {
|
||||||
// Unindex a part of stale indices and forward index tail to HEAD-limit
|
// Unindex a part of stale indices and forward index tail to HEAD-limit
|
||||||
rawdb.UnindexTransactions(bc.db, *tail, head-bc.txLookupLimit+1)
|
rawdb.UnindexTransactions(bc.db, *tail, head-bc.txLookupLimit+1, bc.quit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Any reindexing done, start listening to chain events and moving the index window
|
// Any reindexing done, start listening to chain events and moving the index window
|
||||||
|
|
@ -2294,6 +2378,10 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) {
|
||||||
case <-done:
|
case <-done:
|
||||||
done = nil
|
done = nil
|
||||||
case <-bc.quit:
|
case <-bc.quit:
|
||||||
|
if done != nil {
|
||||||
|
log.Info("Waiting background transaction indexer to exit")
|
||||||
|
<-done
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ func (st *insertStats) report(chain []*types.Block, index int, dirty common.Stor
|
||||||
// Fetch the timings for the batch
|
// Fetch the timings for the batch
|
||||||
var (
|
var (
|
||||||
now = mclock.Now()
|
now = mclock.Now()
|
||||||
elapsed = time.Duration(now) - time.Duration(st.startTime)
|
elapsed = now.Sub(st.startTime)
|
||||||
)
|
)
|
||||||
// If we're at the last block of the batch or report period reached, log
|
// If we're at the last block of the batch or report period reached, log
|
||||||
if index == len(chain)-1 || elapsed >= statsReportLimit {
|
if index == len(chain)-1 || elapsed >= statsReportLimit {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
|
|
@ -38,7 +39,10 @@ import (
|
||||||
// committed to disk and then the process crashed. In this case we expect the full
|
// committed to disk and then the process crashed. In this case we expect the full
|
||||||
// chain to be rolled back to the committed block, but the chain data itself left
|
// chain to be rolled back to the committed block, but the chain data itself left
|
||||||
// in the database for replaying.
|
// in the database for replaying.
|
||||||
func TestShortRepair(t *testing.T) {
|
func TestShortRepair(t *testing.T) { testShortRepair(t, false) }
|
||||||
|
func TestShortRepairWithSnapshots(t *testing.T) { testShortRepair(t, true) }
|
||||||
|
|
||||||
|
func testShortRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -68,14 +72,17 @@ func TestShortRepair(t *testing.T) {
|
||||||
expHeadHeader: 8,
|
expHeadHeader: 8,
|
||||||
expHeadFastBlock: 8,
|
expHeadFastBlock: 8,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a short canonical chain where the fast sync pivot point was
|
// Tests a recovery for a short canonical chain where the fast sync pivot point was
|
||||||
// already committed, after which the process crashed. In this case we expect the full
|
// already committed, after which the process crashed. In this case we expect the full
|
||||||
// chain to be rolled back to the committed block, but the chain data itself left in
|
// chain to be rolled back to the committed block, but the chain data itself left in
|
||||||
// the database for replaying.
|
// the database for replaying.
|
||||||
func TestShortFastSyncedRepair(t *testing.T) {
|
func TestShortFastSyncedRepair(t *testing.T) { testShortFastSyncedRepair(t, false) }
|
||||||
|
func TestShortFastSyncedRepairWithSnapshots(t *testing.T) { testShortFastSyncedRepair(t, true) }
|
||||||
|
|
||||||
|
func testShortFastSyncedRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -105,14 +112,17 @@ func TestShortFastSyncedRepair(t *testing.T) {
|
||||||
expHeadHeader: 8,
|
expHeadHeader: 8,
|
||||||
expHeadFastBlock: 8,
|
expHeadFastBlock: 8,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a short canonical chain where the fast sync pivot point was
|
// Tests a recovery for a short canonical chain where the fast sync pivot point was
|
||||||
// not yet committed, but the process crashed. In this case we expect the chain to
|
// not yet committed, but the process crashed. In this case we expect the chain to
|
||||||
// detect that it was fast syncing and not delete anything, since we can just pick
|
// detect that it was fast syncing and not delete anything, since we can just pick
|
||||||
// up directly where we left off.
|
// up directly where we left off.
|
||||||
func TestShortFastSyncingRepair(t *testing.T) {
|
func TestShortFastSyncingRepair(t *testing.T) { testShortFastSyncingRepair(t, false) }
|
||||||
|
func TestShortFastSyncingRepairWithSnapshots(t *testing.T) { testShortFastSyncingRepair(t, true) }
|
||||||
|
|
||||||
|
func testShortFastSyncingRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -142,7 +152,7 @@ func TestShortFastSyncingRepair(t *testing.T) {
|
||||||
expHeadHeader: 8,
|
expHeadHeader: 8,
|
||||||
expHeadFastBlock: 8,
|
expHeadFastBlock: 8,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a short canonical chain and a shorter side chain, where a
|
// Tests a recovery for a short canonical chain and a shorter side chain, where a
|
||||||
|
|
@ -150,7 +160,10 @@ func TestShortFastSyncingRepair(t *testing.T) {
|
||||||
// test scenario the side chain is below the committed block. In this case we expect
|
// test scenario the side chain is below the committed block. In this case we expect
|
||||||
// the canonical chain to be rolled back to the committed block, but the chain data
|
// the canonical chain to be rolled back to the committed block, but the chain data
|
||||||
// itself left in the database for replaying.
|
// itself left in the database for replaying.
|
||||||
func TestShortOldForkedRepair(t *testing.T) {
|
func TestShortOldForkedRepair(t *testing.T) { testShortOldForkedRepair(t, false) }
|
||||||
|
func TestShortOldForkedRepairWithSnapshots(t *testing.T) { testShortOldForkedRepair(t, true) }
|
||||||
|
|
||||||
|
func testShortOldForkedRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -182,7 +195,7 @@ func TestShortOldForkedRepair(t *testing.T) {
|
||||||
expHeadHeader: 8,
|
expHeadHeader: 8,
|
||||||
expHeadFastBlock: 8,
|
expHeadFastBlock: 8,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a short canonical chain and a shorter side chain, where
|
// Tests a recovery for a short canonical chain and a shorter side chain, where
|
||||||
|
|
@ -191,6 +204,13 @@ func TestShortOldForkedRepair(t *testing.T) {
|
||||||
// this case we expect the canonical chain to be rolled back to the committed block,
|
// this case we expect the canonical chain to be rolled back to the committed block,
|
||||||
// but the chain data itself left in the database for replaying.
|
// but the chain data itself left in the database for replaying.
|
||||||
func TestShortOldForkedFastSyncedRepair(t *testing.T) {
|
func TestShortOldForkedFastSyncedRepair(t *testing.T) {
|
||||||
|
testShortOldForkedFastSyncedRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestShortOldForkedFastSyncedRepairWithSnapshots(t *testing.T) {
|
||||||
|
testShortOldForkedFastSyncedRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShortOldForkedFastSyncedRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -222,7 +242,7 @@ func TestShortOldForkedFastSyncedRepair(t *testing.T) {
|
||||||
expHeadHeader: 8,
|
expHeadHeader: 8,
|
||||||
expHeadFastBlock: 8,
|
expHeadFastBlock: 8,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a short canonical chain and a shorter side chain, where
|
// Tests a recovery for a short canonical chain and a shorter side chain, where
|
||||||
|
|
@ -231,6 +251,13 @@ func TestShortOldForkedFastSyncedRepair(t *testing.T) {
|
||||||
// the chain to detect that it was fast syncing and not delete anything, since we
|
// the chain to detect that it was fast syncing and not delete anything, since we
|
||||||
// can just pick up directly where we left off.
|
// can just pick up directly where we left off.
|
||||||
func TestShortOldForkedFastSyncingRepair(t *testing.T) {
|
func TestShortOldForkedFastSyncingRepair(t *testing.T) {
|
||||||
|
testShortOldForkedFastSyncingRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestShortOldForkedFastSyncingRepairWithSnapshots(t *testing.T) {
|
||||||
|
testShortOldForkedFastSyncingRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShortOldForkedFastSyncingRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -262,7 +289,7 @@ func TestShortOldForkedFastSyncingRepair(t *testing.T) {
|
||||||
expHeadHeader: 8,
|
expHeadHeader: 8,
|
||||||
expHeadFastBlock: 8,
|
expHeadFastBlock: 8,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a short canonical chain and a shorter side chain, where a
|
// Tests a recovery for a short canonical chain and a shorter side chain, where a
|
||||||
|
|
@ -270,7 +297,10 @@ func TestShortOldForkedFastSyncingRepair(t *testing.T) {
|
||||||
// test scenario the side chain reaches above the committed block. In this case we
|
// test scenario the side chain reaches above the committed block. In this case we
|
||||||
// expect the canonical chain to be rolled back to the committed block, but the
|
// expect the canonical chain to be rolled back to the committed block, but the
|
||||||
// chain data itself left in the database for replaying.
|
// chain data itself left in the database for replaying.
|
||||||
func TestShortNewlyForkedRepair(t *testing.T) {
|
func TestShortNewlyForkedRepair(t *testing.T) { testShortNewlyForkedRepair(t, false) }
|
||||||
|
func TestShortNewlyForkedRepairWithSnapshots(t *testing.T) { testShortNewlyForkedRepair(t, true) }
|
||||||
|
|
||||||
|
func testShortNewlyForkedRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6
|
// └->S1->S2->S3->S4->S5->S6
|
||||||
|
|
@ -302,7 +332,7 @@ func TestShortNewlyForkedRepair(t *testing.T) {
|
||||||
expHeadHeader: 8,
|
expHeadHeader: 8,
|
||||||
expHeadFastBlock: 8,
|
expHeadFastBlock: 8,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a short canonical chain and a shorter side chain, where
|
// Tests a recovery for a short canonical chain and a shorter side chain, where
|
||||||
|
|
@ -311,6 +341,13 @@ func TestShortNewlyForkedRepair(t *testing.T) {
|
||||||
// In this case we expect the canonical chain to be rolled back to the committed
|
// In this case we expect the canonical chain to be rolled back to the committed
|
||||||
// block, but the chain data itself left in the database for replaying.
|
// block, but the chain data itself left in the database for replaying.
|
||||||
func TestShortNewlyForkedFastSyncedRepair(t *testing.T) {
|
func TestShortNewlyForkedFastSyncedRepair(t *testing.T) {
|
||||||
|
testShortNewlyForkedFastSyncedRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestShortNewlyForkedFastSyncedRepairWithSnapshots(t *testing.T) {
|
||||||
|
testShortNewlyForkedFastSyncedRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShortNewlyForkedFastSyncedRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6
|
// └->S1->S2->S3->S4->S5->S6
|
||||||
|
|
@ -342,7 +379,7 @@ func TestShortNewlyForkedFastSyncedRepair(t *testing.T) {
|
||||||
expHeadHeader: 8,
|
expHeadHeader: 8,
|
||||||
expHeadFastBlock: 8,
|
expHeadFastBlock: 8,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a short canonical chain and a shorter side chain, where
|
// Tests a recovery for a short canonical chain and a shorter side chain, where
|
||||||
|
|
@ -351,6 +388,13 @@ func TestShortNewlyForkedFastSyncedRepair(t *testing.T) {
|
||||||
// case we expect the chain to detect that it was fast syncing and not delete
|
// case we expect the chain to detect that it was fast syncing and not delete
|
||||||
// anything, since we can just pick up directly where we left off.
|
// anything, since we can just pick up directly where we left off.
|
||||||
func TestShortNewlyForkedFastSyncingRepair(t *testing.T) {
|
func TestShortNewlyForkedFastSyncingRepair(t *testing.T) {
|
||||||
|
testShortNewlyForkedFastSyncingRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestShortNewlyForkedFastSyncingRepairWithSnapshots(t *testing.T) {
|
||||||
|
testShortNewlyForkedFastSyncingRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShortNewlyForkedFastSyncingRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6
|
// └->S1->S2->S3->S4->S5->S6
|
||||||
|
|
@ -382,14 +426,17 @@ func TestShortNewlyForkedFastSyncingRepair(t *testing.T) {
|
||||||
expHeadHeader: 8,
|
expHeadHeader: 8,
|
||||||
expHeadFastBlock: 8,
|
expHeadFastBlock: 8,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a short canonical chain and a longer side chain, where a
|
// Tests a recovery for a short canonical chain and a longer side chain, where a
|
||||||
// recent block was already committed to disk and then the process crashed. In this
|
// recent block was already committed to disk and then the process crashed. In this
|
||||||
// case we expect the canonical chain to be rolled back to the committed block, but
|
// case we expect the canonical chain to be rolled back to the committed block, but
|
||||||
// the chain data itself left in the database for replaying.
|
// the chain data itself left in the database for replaying.
|
||||||
func TestShortReorgedRepair(t *testing.T) {
|
func TestShortReorgedRepair(t *testing.T) { testShortReorgedRepair(t, false) }
|
||||||
|
func TestShortReorgedRepairWithSnapshots(t *testing.T) { testShortReorgedRepair(t, true) }
|
||||||
|
|
||||||
|
func testShortReorgedRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
||||||
|
|
@ -421,7 +468,7 @@ func TestShortReorgedRepair(t *testing.T) {
|
||||||
expHeadHeader: 8,
|
expHeadHeader: 8,
|
||||||
expHeadFastBlock: 8,
|
expHeadFastBlock: 8,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a short canonical chain and a longer side chain, where
|
// Tests a recovery for a short canonical chain and a longer side chain, where
|
||||||
|
|
@ -429,6 +476,13 @@ func TestShortReorgedRepair(t *testing.T) {
|
||||||
// crashed. In this case we expect the canonical chain to be rolled back to the
|
// crashed. In this case we expect the canonical chain to be rolled back to the
|
||||||
// committed block, but the chain data itself left in the database for replaying.
|
// committed block, but the chain data itself left in the database for replaying.
|
||||||
func TestShortReorgedFastSyncedRepair(t *testing.T) {
|
func TestShortReorgedFastSyncedRepair(t *testing.T) {
|
||||||
|
testShortReorgedFastSyncedRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestShortReorgedFastSyncedRepairWithSnapshots(t *testing.T) {
|
||||||
|
testShortReorgedFastSyncedRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShortReorgedFastSyncedRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
||||||
|
|
@ -460,7 +514,7 @@ func TestShortReorgedFastSyncedRepair(t *testing.T) {
|
||||||
expHeadHeader: 8,
|
expHeadHeader: 8,
|
||||||
expHeadFastBlock: 8,
|
expHeadFastBlock: 8,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a short canonical chain and a longer side chain, where
|
// Tests a recovery for a short canonical chain and a longer side chain, where
|
||||||
|
|
@ -468,6 +522,13 @@ func TestShortReorgedFastSyncedRepair(t *testing.T) {
|
||||||
// this case we expect the chain to detect that it was fast syncing and not delete
|
// this case we expect the chain to detect that it was fast syncing and not delete
|
||||||
// anything, since we can just pick up directly where we left off.
|
// anything, since we can just pick up directly where we left off.
|
||||||
func TestShortReorgedFastSyncingRepair(t *testing.T) {
|
func TestShortReorgedFastSyncingRepair(t *testing.T) {
|
||||||
|
testShortReorgedFastSyncingRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestShortReorgedFastSyncingRepairWithSnapshots(t *testing.T) {
|
||||||
|
testShortReorgedFastSyncingRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShortReorgedFastSyncingRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
||||||
|
|
@ -499,14 +560,17 @@ func TestShortReorgedFastSyncingRepair(t *testing.T) {
|
||||||
expHeadHeader: 8,
|
expHeadHeader: 8,
|
||||||
expHeadFastBlock: 8,
|
expHeadFastBlock: 8,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks where a recent
|
// Tests a recovery for a long canonical chain with frozen blocks where a recent
|
||||||
// block - newer than the ancient limit - was already committed to disk and then
|
// block - newer than the ancient limit - was already committed to disk and then
|
||||||
// the process crashed. In this case we expect the chain to be rolled back to the
|
// the process crashed. In this case we expect the chain to be rolled back to the
|
||||||
// committed block, with everything afterwads kept as fast sync data.
|
// committed block, with everything afterwads kept as fast sync data.
|
||||||
func TestLongShallowRepair(t *testing.T) {
|
func TestLongShallowRepair(t *testing.T) { testLongShallowRepair(t, false) }
|
||||||
|
func TestLongShallowRepairWithSnapshots(t *testing.T) { testLongShallowRepair(t, true) }
|
||||||
|
|
||||||
|
func testLongShallowRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -541,14 +605,17 @@ func TestLongShallowRepair(t *testing.T) {
|
||||||
expHeadHeader: 18,
|
expHeadHeader: 18,
|
||||||
expHeadFastBlock: 18,
|
expHeadFastBlock: 18,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks where a recent
|
// Tests a recovery for a long canonical chain with frozen blocks where a recent
|
||||||
// block - older than the ancient limit - was already committed to disk and then
|
// block - older than the ancient limit - was already committed to disk and then
|
||||||
// the process crashed. In this case we expect the chain to be rolled back to the
|
// the process crashed. In this case we expect the chain to be rolled back to the
|
||||||
// committed block, with everything afterwads deleted.
|
// committed block, with everything afterwads deleted.
|
||||||
func TestLongDeepRepair(t *testing.T) {
|
func TestLongDeepRepair(t *testing.T) { testLongDeepRepair(t, false) }
|
||||||
|
func TestLongDeepRepairWithSnapshots(t *testing.T) { testLongDeepRepair(t, true) }
|
||||||
|
|
||||||
|
func testLongDeepRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -582,7 +649,7 @@ func TestLongDeepRepair(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks where the fast
|
// Tests a recovery for a long canonical chain with frozen blocks where the fast
|
||||||
|
|
@ -590,6 +657,13 @@ func TestLongDeepRepair(t *testing.T) {
|
||||||
// which the process crashed. In this case we expect the chain to be rolled back
|
// which the process crashed. In this case we expect the chain to be rolled back
|
||||||
// to the committed block, with everything afterwads kept as fast sync data.
|
// to the committed block, with everything afterwads kept as fast sync data.
|
||||||
func TestLongFastSyncedShallowRepair(t *testing.T) {
|
func TestLongFastSyncedShallowRepair(t *testing.T) {
|
||||||
|
testLongFastSyncedShallowRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongFastSyncedShallowRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongFastSyncedShallowRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongFastSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -624,14 +698,17 @@ func TestLongFastSyncedShallowRepair(t *testing.T) {
|
||||||
expHeadHeader: 18,
|
expHeadHeader: 18,
|
||||||
expHeadFastBlock: 18,
|
expHeadFastBlock: 18,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks where the fast
|
// Tests a recovery for a long canonical chain with frozen blocks where the fast
|
||||||
// sync pivot point - older than the ancient limit - was already committed, after
|
// sync pivot point - older than the ancient limit - was already committed, after
|
||||||
// which the process crashed. In this case we expect the chain to be rolled back
|
// which the process crashed. In this case we expect the chain to be rolled back
|
||||||
// to the committed block, with everything afterwads deleted.
|
// to the committed block, with everything afterwads deleted.
|
||||||
func TestLongFastSyncedDeepRepair(t *testing.T) {
|
func TestLongFastSyncedDeepRepair(t *testing.T) { testLongFastSyncedDeepRepair(t, false) }
|
||||||
|
func TestLongFastSyncedDeepRepairWithSnapshots(t *testing.T) { testLongFastSyncedDeepRepair(t, true) }
|
||||||
|
|
||||||
|
func testLongFastSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -665,7 +742,7 @@ func TestLongFastSyncedDeepRepair(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks where the fast
|
// Tests a recovery for a long canonical chain with frozen blocks where the fast
|
||||||
|
|
@ -674,6 +751,13 @@ func TestLongFastSyncedDeepRepair(t *testing.T) {
|
||||||
// syncing and not delete anything, since we can just pick up directly where we
|
// syncing and not delete anything, since we can just pick up directly where we
|
||||||
// left off.
|
// left off.
|
||||||
func TestLongFastSyncingShallowRepair(t *testing.T) {
|
func TestLongFastSyncingShallowRepair(t *testing.T) {
|
||||||
|
testLongFastSyncingShallowRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongFastSyncingShallowRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongFastSyncingShallowRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongFastSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -708,7 +792,7 @@ func TestLongFastSyncingShallowRepair(t *testing.T) {
|
||||||
expHeadHeader: 18,
|
expHeadHeader: 18,
|
||||||
expHeadFastBlock: 18,
|
expHeadFastBlock: 18,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks where the fast
|
// Tests a recovery for a long canonical chain with frozen blocks where the fast
|
||||||
|
|
@ -716,7 +800,10 @@ func TestLongFastSyncingShallowRepair(t *testing.T) {
|
||||||
// process crashed. In this case we expect the chain to detect that it was fast
|
// process crashed. In this case we expect the chain to detect that it was fast
|
||||||
// syncing and not delete anything, since we can just pick up directly where we
|
// syncing and not delete anything, since we can just pick up directly where we
|
||||||
// left off.
|
// left off.
|
||||||
func TestLongFastSyncingDeepRepair(t *testing.T) {
|
func TestLongFastSyncingDeepRepair(t *testing.T) { testLongFastSyncingDeepRepair(t, false) }
|
||||||
|
func TestLongFastSyncingDeepRepairWithSnapshots(t *testing.T) { testLongFastSyncingDeepRepair(t, true) }
|
||||||
|
|
||||||
|
func testLongFastSyncingDeepRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -751,7 +838,7 @@ func TestLongFastSyncingDeepRepair(t *testing.T) {
|
||||||
expHeadHeader: 24,
|
expHeadHeader: 24,
|
||||||
expHeadFastBlock: 24,
|
expHeadFastBlock: 24,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -761,6 +848,13 @@ func TestLongFastSyncingDeepRepair(t *testing.T) {
|
||||||
// rolled back to the committed block, with everything afterwads kept as fast
|
// rolled back to the committed block, with everything afterwads kept as fast
|
||||||
// sync data; the side chain completely nuked by the freezer.
|
// sync data; the side chain completely nuked by the freezer.
|
||||||
func TestLongOldForkedShallowRepair(t *testing.T) {
|
func TestLongOldForkedShallowRepair(t *testing.T) {
|
||||||
|
testLongOldForkedShallowRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongOldForkedShallowRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongOldForkedShallowRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongOldForkedShallowRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -796,7 +890,7 @@ func TestLongOldForkedShallowRepair(t *testing.T) {
|
||||||
expHeadHeader: 18,
|
expHeadHeader: 18,
|
||||||
expHeadFastBlock: 18,
|
expHeadFastBlock: 18,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -805,7 +899,10 @@ func TestLongOldForkedShallowRepair(t *testing.T) {
|
||||||
// chain is below the committed block. In this case we expect the canonical chain
|
// chain is below the committed block. In this case we expect the canonical chain
|
||||||
// to be rolled back to the committed block, with everything afterwads deleted;
|
// to be rolled back to the committed block, with everything afterwads deleted;
|
||||||
// the side chain completely nuked by the freezer.
|
// the side chain completely nuked by the freezer.
|
||||||
func TestLongOldForkedDeepRepair(t *testing.T) {
|
func TestLongOldForkedDeepRepair(t *testing.T) { testLongOldForkedDeepRepair(t, false) }
|
||||||
|
func TestLongOldForkedDeepRepairWithSnapshots(t *testing.T) { testLongOldForkedDeepRepair(t, true) }
|
||||||
|
|
||||||
|
func testLongOldForkedDeepRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -840,7 +937,7 @@ func TestLongOldForkedDeepRepair(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -850,6 +947,13 @@ func TestLongOldForkedDeepRepair(t *testing.T) {
|
||||||
// to be rolled back to the committed block, with everything afterwads kept as
|
// to be rolled back to the committed block, with everything afterwads kept as
|
||||||
// fast sync data; the side chain completely nuked by the freezer.
|
// fast sync data; the side chain completely nuked by the freezer.
|
||||||
func TestLongOldForkedFastSyncedShallowRepair(t *testing.T) {
|
func TestLongOldForkedFastSyncedShallowRepair(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncedShallowRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongOldForkedFastSyncedShallowRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncedShallowRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongOldForkedFastSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -885,7 +989,7 @@ func TestLongOldForkedFastSyncedShallowRepair(t *testing.T) {
|
||||||
expHeadHeader: 18,
|
expHeadHeader: 18,
|
||||||
expHeadFastBlock: 18,
|
expHeadFastBlock: 18,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -895,6 +999,13 @@ func TestLongOldForkedFastSyncedShallowRepair(t *testing.T) {
|
||||||
// chain to be rolled back to the committed block, with everything afterwads deleted;
|
// chain to be rolled back to the committed block, with everything afterwads deleted;
|
||||||
// the side chain completely nuked by the freezer.
|
// the side chain completely nuked by the freezer.
|
||||||
func TestLongOldForkedFastSyncedDeepRepair(t *testing.T) {
|
func TestLongOldForkedFastSyncedDeepRepair(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncedDeepRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongOldForkedFastSyncedDeepRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncedDeepRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongOldForkedFastSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -929,7 +1040,7 @@ func TestLongOldForkedFastSyncedDeepRepair(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -939,6 +1050,13 @@ func TestLongOldForkedFastSyncedDeepRepair(t *testing.T) {
|
||||||
// that it was fast syncing and not delete anything. The side chain is completely
|
// that it was fast syncing and not delete anything. The side chain is completely
|
||||||
// nuked by the freezer.
|
// nuked by the freezer.
|
||||||
func TestLongOldForkedFastSyncingShallowRepair(t *testing.T) {
|
func TestLongOldForkedFastSyncingShallowRepair(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncingShallowRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongOldForkedFastSyncingShallowRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncingShallowRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongOldForkedFastSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -974,7 +1092,7 @@ func TestLongOldForkedFastSyncingShallowRepair(t *testing.T) {
|
||||||
expHeadHeader: 18,
|
expHeadHeader: 18,
|
||||||
expHeadFastBlock: 18,
|
expHeadFastBlock: 18,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -984,6 +1102,13 @@ func TestLongOldForkedFastSyncingShallowRepair(t *testing.T) {
|
||||||
// that it was fast syncing and not delete anything. The side chain is completely
|
// that it was fast syncing and not delete anything. The side chain is completely
|
||||||
// nuked by the freezer.
|
// nuked by the freezer.
|
||||||
func TestLongOldForkedFastSyncingDeepRepair(t *testing.T) {
|
func TestLongOldForkedFastSyncingDeepRepair(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncingDeepRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongOldForkedFastSyncingDeepRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncingDeepRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongOldForkedFastSyncingDeepRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -1019,7 +1144,7 @@ func TestLongOldForkedFastSyncingDeepRepair(t *testing.T) {
|
||||||
expHeadHeader: 24,
|
expHeadHeader: 24,
|
||||||
expHeadFastBlock: 24,
|
expHeadFastBlock: 24,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1029,6 +1154,13 @@ func TestLongOldForkedFastSyncingDeepRepair(t *testing.T) {
|
||||||
// rolled back to the committed block, with everything afterwads kept as fast
|
// rolled back to the committed block, with everything afterwads kept as fast
|
||||||
// sync data; the side chain completely nuked by the freezer.
|
// sync data; the side chain completely nuked by the freezer.
|
||||||
func TestLongNewerForkedShallowRepair(t *testing.T) {
|
func TestLongNewerForkedShallowRepair(t *testing.T) {
|
||||||
|
testLongNewerForkedShallowRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongNewerForkedShallowRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongNewerForkedShallowRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongNewerForkedShallowRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||||
|
|
@ -1064,7 +1196,7 @@ func TestLongNewerForkedShallowRepair(t *testing.T) {
|
||||||
expHeadHeader: 18,
|
expHeadHeader: 18,
|
||||||
expHeadFastBlock: 18,
|
expHeadFastBlock: 18,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1073,7 +1205,10 @@ func TestLongNewerForkedShallowRepair(t *testing.T) {
|
||||||
// chain is above the committed block. In this case we expect the canonical chain
|
// chain is above the committed block. In this case we expect the canonical chain
|
||||||
// to be rolled back to the committed block, with everything afterwads deleted;
|
// to be rolled back to the committed block, with everything afterwads deleted;
|
||||||
// the side chain completely nuked by the freezer.
|
// the side chain completely nuked by the freezer.
|
||||||
func TestLongNewerForkedDeepRepair(t *testing.T) {
|
func TestLongNewerForkedDeepRepair(t *testing.T) { testLongNewerForkedDeepRepair(t, false) }
|
||||||
|
func TestLongNewerForkedDeepRepairWithSnapshots(t *testing.T) { testLongNewerForkedDeepRepair(t, true) }
|
||||||
|
|
||||||
|
func testLongNewerForkedDeepRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||||
|
|
@ -1108,7 +1243,7 @@ func TestLongNewerForkedDeepRepair(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1118,6 +1253,13 @@ func TestLongNewerForkedDeepRepair(t *testing.T) {
|
||||||
// to be rolled back to the committed block, with everything afterwads kept as fast
|
// to be rolled back to the committed block, with everything afterwads kept as fast
|
||||||
// sync data; the side chain completely nuked by the freezer.
|
// sync data; the side chain completely nuked by the freezer.
|
||||||
func TestLongNewerForkedFastSyncedShallowRepair(t *testing.T) {
|
func TestLongNewerForkedFastSyncedShallowRepair(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncedShallowRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongNewerForkedFastSyncedShallowRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncedShallowRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongNewerForkedFastSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||||
|
|
@ -1153,7 +1295,7 @@ func TestLongNewerForkedFastSyncedShallowRepair(t *testing.T) {
|
||||||
expHeadHeader: 18,
|
expHeadHeader: 18,
|
||||||
expHeadFastBlock: 18,
|
expHeadFastBlock: 18,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1163,6 +1305,13 @@ func TestLongNewerForkedFastSyncedShallowRepair(t *testing.T) {
|
||||||
// chain to be rolled back to the committed block, with everything afterwads deleted;
|
// chain to be rolled back to the committed block, with everything afterwads deleted;
|
||||||
// the side chain completely nuked by the freezer.
|
// the side chain completely nuked by the freezer.
|
||||||
func TestLongNewerForkedFastSyncedDeepRepair(t *testing.T) {
|
func TestLongNewerForkedFastSyncedDeepRepair(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncedDeepRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongNewerForkedFastSyncedDeepRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncedDeepRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongNewerForkedFastSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||||
|
|
@ -1197,7 +1346,7 @@ func TestLongNewerForkedFastSyncedDeepRepair(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1207,6 +1356,13 @@ func TestLongNewerForkedFastSyncedDeepRepair(t *testing.T) {
|
||||||
// that it was fast syncing and not delete anything. The side chain is completely
|
// that it was fast syncing and not delete anything. The side chain is completely
|
||||||
// nuked by the freezer.
|
// nuked by the freezer.
|
||||||
func TestLongNewerForkedFastSyncingShallowRepair(t *testing.T) {
|
func TestLongNewerForkedFastSyncingShallowRepair(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncingShallowRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongNewerForkedFastSyncingShallowRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncingShallowRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongNewerForkedFastSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||||
|
|
@ -1242,7 +1398,7 @@ func TestLongNewerForkedFastSyncingShallowRepair(t *testing.T) {
|
||||||
expHeadHeader: 18,
|
expHeadHeader: 18,
|
||||||
expHeadFastBlock: 18,
|
expHeadFastBlock: 18,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
// Tests a recovery for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1252,6 +1408,13 @@ func TestLongNewerForkedFastSyncingShallowRepair(t *testing.T) {
|
||||||
// that it was fast syncing and not delete anything. The side chain is completely
|
// that it was fast syncing and not delete anything. The side chain is completely
|
||||||
// nuked by the freezer.
|
// nuked by the freezer.
|
||||||
func TestLongNewerForkedFastSyncingDeepRepair(t *testing.T) {
|
func TestLongNewerForkedFastSyncingDeepRepair(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncingDeepRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongNewerForkedFastSyncingDeepRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncingDeepRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongNewerForkedFastSyncingDeepRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||||
|
|
@ -1287,7 +1450,7 @@ func TestLongNewerForkedFastSyncingDeepRepair(t *testing.T) {
|
||||||
expHeadHeader: 24,
|
expHeadHeader: 24,
|
||||||
expHeadFastBlock: 24,
|
expHeadFastBlock: 24,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a longer side
|
// Tests a recovery for a long canonical chain with frozen blocks and a longer side
|
||||||
|
|
@ -1295,7 +1458,10 @@ func TestLongNewerForkedFastSyncingDeepRepair(t *testing.T) {
|
||||||
// to disk and then the process crashed. In this case we expect the chain to be
|
// to disk and then the process crashed. In this case we expect the chain to be
|
||||||
// rolled back to the committed block, with everything afterwads kept as fast sync
|
// rolled back to the committed block, with everything afterwads kept as fast sync
|
||||||
// data. The side chain completely nuked by the freezer.
|
// data. The side chain completely nuked by the freezer.
|
||||||
func TestLongReorgedShallowRepair(t *testing.T) {
|
func TestLongReorgedShallowRepair(t *testing.T) { testLongReorgedShallowRepair(t, false) }
|
||||||
|
func TestLongReorgedShallowRepairWithSnapshots(t *testing.T) { testLongReorgedShallowRepair(t, true) }
|
||||||
|
|
||||||
|
func testLongReorgedShallowRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||||
|
|
@ -1331,7 +1497,7 @@ func TestLongReorgedShallowRepair(t *testing.T) {
|
||||||
expHeadHeader: 18,
|
expHeadHeader: 18,
|
||||||
expHeadFastBlock: 18,
|
expHeadFastBlock: 18,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a longer side
|
// Tests a recovery for a long canonical chain with frozen blocks and a longer side
|
||||||
|
|
@ -1339,7 +1505,10 @@ func TestLongReorgedShallowRepair(t *testing.T) {
|
||||||
// to disk and then the process crashed. In this case we expect the canonical chains
|
// to disk and then the process crashed. In this case we expect the canonical chains
|
||||||
// to be rolled back to the committed block, with everything afterwads deleted. The
|
// to be rolled back to the committed block, with everything afterwads deleted. The
|
||||||
// side chain completely nuked by the freezer.
|
// side chain completely nuked by the freezer.
|
||||||
func TestLongReorgedDeepRepair(t *testing.T) {
|
func TestLongReorgedDeepRepair(t *testing.T) { testLongReorgedDeepRepair(t, false) }
|
||||||
|
func TestLongReorgedDeepRepairWithSnapshots(t *testing.T) { testLongReorgedDeepRepair(t, true) }
|
||||||
|
|
||||||
|
func testLongReorgedDeepRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||||
|
|
@ -1374,7 +1543,7 @@ func TestLongReorgedDeepRepair(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a longer
|
// Tests a recovery for a long canonical chain with frozen blocks and a longer
|
||||||
|
|
@ -1384,6 +1553,13 @@ func TestLongReorgedDeepRepair(t *testing.T) {
|
||||||
// afterwads kept as fast sync data. The side chain completely nuked by the
|
// afterwads kept as fast sync data. The side chain completely nuked by the
|
||||||
// freezer.
|
// freezer.
|
||||||
func TestLongReorgedFastSyncedShallowRepair(t *testing.T) {
|
func TestLongReorgedFastSyncedShallowRepair(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncedShallowRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongReorgedFastSyncedShallowRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncedShallowRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongReorgedFastSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||||
|
|
@ -1419,7 +1595,7 @@ func TestLongReorgedFastSyncedShallowRepair(t *testing.T) {
|
||||||
expHeadHeader: 18,
|
expHeadHeader: 18,
|
||||||
expHeadFastBlock: 18,
|
expHeadFastBlock: 18,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a longer
|
// Tests a recovery for a long canonical chain with frozen blocks and a longer
|
||||||
|
|
@ -1428,6 +1604,13 @@ func TestLongReorgedFastSyncedShallowRepair(t *testing.T) {
|
||||||
// expect the canonical chains to be rolled back to the committed block, with
|
// expect the canonical chains to be rolled back to the committed block, with
|
||||||
// everything afterwads deleted. The side chain completely nuked by the freezer.
|
// everything afterwads deleted. The side chain completely nuked by the freezer.
|
||||||
func TestLongReorgedFastSyncedDeepRepair(t *testing.T) {
|
func TestLongReorgedFastSyncedDeepRepair(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncedDeepRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongReorgedFastSyncedDeepRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncedDeepRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongReorgedFastSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||||
|
|
@ -1462,7 +1645,7 @@ func TestLongReorgedFastSyncedDeepRepair(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a longer
|
// Tests a recovery for a long canonical chain with frozen blocks and a longer
|
||||||
|
|
@ -1471,6 +1654,13 @@ func TestLongReorgedFastSyncedDeepRepair(t *testing.T) {
|
||||||
// chain to detect that it was fast syncing and not delete anything, since we
|
// chain to detect that it was fast syncing and not delete anything, since we
|
||||||
// can just pick up directly where we left off.
|
// can just pick up directly where we left off.
|
||||||
func TestLongReorgedFastSyncingShallowRepair(t *testing.T) {
|
func TestLongReorgedFastSyncingShallowRepair(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncingShallowRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongReorgedFastSyncingShallowRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncingShallowRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongReorgedFastSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||||
|
|
@ -1506,7 +1696,7 @@ func TestLongReorgedFastSyncingShallowRepair(t *testing.T) {
|
||||||
expHeadHeader: 18,
|
expHeadHeader: 18,
|
||||||
expHeadFastBlock: 18,
|
expHeadFastBlock: 18,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a recovery for a long canonical chain with frozen blocks and a longer
|
// Tests a recovery for a long canonical chain with frozen blocks and a longer
|
||||||
|
|
@ -1515,6 +1705,13 @@ func TestLongReorgedFastSyncingShallowRepair(t *testing.T) {
|
||||||
// chain to detect that it was fast syncing and not delete anything, since we
|
// chain to detect that it was fast syncing and not delete anything, since we
|
||||||
// can just pick up directly where we left off.
|
// can just pick up directly where we left off.
|
||||||
func TestLongReorgedFastSyncingDeepRepair(t *testing.T) {
|
func TestLongReorgedFastSyncingDeepRepair(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncingDeepRepair(t, false)
|
||||||
|
}
|
||||||
|
func TestLongReorgedFastSyncingDeepRepairWithSnapshots(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncingDeepRepair(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongReorgedFastSyncingDeepRepair(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||||
|
|
@ -1550,13 +1747,13 @@ func TestLongReorgedFastSyncingDeepRepair(t *testing.T) {
|
||||||
expHeadHeader: 24,
|
expHeadHeader: 24,
|
||||||
expHeadFastBlock: 24,
|
expHeadFastBlock: 24,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRepair(t *testing.T, tt *rewindTest) {
|
func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
|
||||||
// It's hard to follow the test case, visualize the input
|
// It's hard to follow the test case, visualize the input
|
||||||
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
||||||
//fmt.Println(tt.dump(true))
|
// fmt.Println(tt.dump(true))
|
||||||
|
|
||||||
// Create a temporary persistent database
|
// Create a temporary persistent database
|
||||||
datadir, err := ioutil.TempDir("", "")
|
datadir, err := ioutil.TempDir("", "")
|
||||||
|
|
@ -1575,8 +1772,18 @@ func testRepair(t *testing.T, tt *rewindTest) {
|
||||||
var (
|
var (
|
||||||
genesis = new(Genesis).MustCommit(db)
|
genesis = new(Genesis).MustCommit(db)
|
||||||
engine = ethash.NewFullFaker()
|
engine = ethash.NewFullFaker()
|
||||||
|
config = &CacheConfig{
|
||||||
|
TrieCleanLimit: 256,
|
||||||
|
TrieDirtyLimit: 256,
|
||||||
|
TrieTimeLimit: 5 * time.Minute,
|
||||||
|
SnapshotLimit: 0, // Disable snapshot by default
|
||||||
|
}
|
||||||
)
|
)
|
||||||
chain, err := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
if snapshots {
|
||||||
|
config.SnapshotLimit = 256
|
||||||
|
config.SnapshotWait = true
|
||||||
|
}
|
||||||
|
chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create chain: %v", err)
|
t.Fatalf("Failed to create chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1599,6 +1806,11 @@ func testRepair(t *testing.T, tt *rewindTest) {
|
||||||
}
|
}
|
||||||
if tt.commitBlock > 0 {
|
if tt.commitBlock > 0 {
|
||||||
chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil)
|
chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil)
|
||||||
|
if snapshots {
|
||||||
|
if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil {
|
||||||
|
t.Fatalf("Failed to flatten snapshots: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if _, err := chain.InsertChain(canonblocks[tt.commitBlock:]); err != nil {
|
if _, err := chain.InsertChain(canonblocks[tt.commitBlock:]); err != nil {
|
||||||
t.Fatalf("Failed to import canonical chain tail: %v", err)
|
t.Fatalf("Failed to import canonical chain tail: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
|
|
@ -150,7 +151,10 @@ func (tt *rewindTest) dump(crash bool) string {
|
||||||
// chain to be rolled back to the committed block. Everything above the sethead
|
// chain to be rolled back to the committed block. Everything above the sethead
|
||||||
// point should be deleted. In between the committed block and the requested head
|
// point should be deleted. In between the committed block and the requested head
|
||||||
// the data can remain as "fast sync" data to avoid redownloading it.
|
// the data can remain as "fast sync" data to avoid redownloading it.
|
||||||
func TestShortSetHead(t *testing.T) {
|
func TestShortSetHead(t *testing.T) { testShortSetHead(t, false) }
|
||||||
|
func TestShortSetHeadWithSnapshots(t *testing.T) { testShortSetHead(t, true) }
|
||||||
|
|
||||||
|
func testShortSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -181,7 +185,7 @@ func TestShortSetHead(t *testing.T) {
|
||||||
expHeadHeader: 7,
|
expHeadHeader: 7,
|
||||||
expHeadFastBlock: 7,
|
expHeadFastBlock: 7,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a short canonical chain where the fast sync pivot point was
|
// Tests a sethead for a short canonical chain where the fast sync pivot point was
|
||||||
|
|
@ -190,7 +194,10 @@ func TestShortSetHead(t *testing.T) {
|
||||||
// Everything above the sethead point should be deleted. In between the committed
|
// Everything above the sethead point should be deleted. In between the committed
|
||||||
// block and the requested head the data can remain as "fast sync" data to avoid
|
// block and the requested head the data can remain as "fast sync" data to avoid
|
||||||
// redownloading it.
|
// redownloading it.
|
||||||
func TestShortFastSyncedSetHead(t *testing.T) {
|
func TestShortFastSyncedSetHead(t *testing.T) { testShortFastSyncedSetHead(t, false) }
|
||||||
|
func TestShortFastSyncedSetHeadWithSnapshots(t *testing.T) { testShortFastSyncedSetHead(t, true) }
|
||||||
|
|
||||||
|
func testShortFastSyncedSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -221,7 +228,7 @@ func TestShortFastSyncedSetHead(t *testing.T) {
|
||||||
expHeadHeader: 7,
|
expHeadHeader: 7,
|
||||||
expHeadFastBlock: 7,
|
expHeadFastBlock: 7,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a short canonical chain where the fast sync pivot point was
|
// Tests a sethead for a short canonical chain where the fast sync pivot point was
|
||||||
|
|
@ -229,7 +236,10 @@ func TestShortFastSyncedSetHead(t *testing.T) {
|
||||||
// detect that it was fast syncing and delete everything from the new head, since
|
// detect that it was fast syncing and delete everything from the new head, since
|
||||||
// we can just pick up fast syncing from there. The head full block should be set
|
// we can just pick up fast syncing from there. The head full block should be set
|
||||||
// to the genesis.
|
// to the genesis.
|
||||||
func TestShortFastSyncingSetHead(t *testing.T) {
|
func TestShortFastSyncingSetHead(t *testing.T) { testShortFastSyncingSetHead(t, false) }
|
||||||
|
func TestShortFastSyncingSetHeadWithSnapshots(t *testing.T) { testShortFastSyncingSetHead(t, true) }
|
||||||
|
|
||||||
|
func testShortFastSyncingSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -260,7 +270,7 @@ func TestShortFastSyncingSetHead(t *testing.T) {
|
||||||
expHeadHeader: 7,
|
expHeadHeader: 7,
|
||||||
expHeadFastBlock: 7,
|
expHeadFastBlock: 7,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a short canonical chain and a shorter side chain, where a
|
// Tests a sethead for a short canonical chain and a shorter side chain, where a
|
||||||
|
|
@ -270,7 +280,10 @@ func TestShortFastSyncingSetHead(t *testing.T) {
|
||||||
// above the sethead point should be deleted. In between the committed block and
|
// above the sethead point should be deleted. In between the committed block and
|
||||||
// the requested head the data can remain as "fast sync" data to avoid redownloading
|
// the requested head the data can remain as "fast sync" data to avoid redownloading
|
||||||
// it. The side chain should be left alone as it was shorter.
|
// it. The side chain should be left alone as it was shorter.
|
||||||
func TestShortOldForkedSetHead(t *testing.T) {
|
func TestShortOldForkedSetHead(t *testing.T) { testShortOldForkedSetHead(t, false) }
|
||||||
|
func TestShortOldForkedSetHeadWithSnapshots(t *testing.T) { testShortOldForkedSetHead(t, true) }
|
||||||
|
|
||||||
|
func testShortOldForkedSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -303,7 +316,7 @@ func TestShortOldForkedSetHead(t *testing.T) {
|
||||||
expHeadHeader: 7,
|
expHeadHeader: 7,
|
||||||
expHeadFastBlock: 7,
|
expHeadFastBlock: 7,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a short canonical chain and a shorter side chain, where
|
// Tests a sethead for a short canonical chain and a shorter side chain, where
|
||||||
|
|
@ -314,6 +327,13 @@ func TestShortOldForkedSetHead(t *testing.T) {
|
||||||
// committed block and the requested head the data can remain as "fast sync" data
|
// committed block and the requested head the data can remain as "fast sync" data
|
||||||
// to avoid redownloading it. The side chain should be left alone as it was shorter.
|
// to avoid redownloading it. The side chain should be left alone as it was shorter.
|
||||||
func TestShortOldForkedFastSyncedSetHead(t *testing.T) {
|
func TestShortOldForkedFastSyncedSetHead(t *testing.T) {
|
||||||
|
testShortOldForkedFastSyncedSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestShortOldForkedFastSyncedSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testShortOldForkedFastSyncedSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShortOldForkedFastSyncedSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -346,7 +366,7 @@ func TestShortOldForkedFastSyncedSetHead(t *testing.T) {
|
||||||
expHeadHeader: 7,
|
expHeadHeader: 7,
|
||||||
expHeadFastBlock: 7,
|
expHeadFastBlock: 7,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a short canonical chain and a shorter side chain, where
|
// Tests a sethead for a short canonical chain and a shorter side chain, where
|
||||||
|
|
@ -356,6 +376,13 @@ func TestShortOldForkedFastSyncedSetHead(t *testing.T) {
|
||||||
// head, since we can just pick up fast syncing from there. The head full block
|
// head, since we can just pick up fast syncing from there. The head full block
|
||||||
// should be set to the genesis.
|
// should be set to the genesis.
|
||||||
func TestShortOldForkedFastSyncingSetHead(t *testing.T) {
|
func TestShortOldForkedFastSyncingSetHead(t *testing.T) {
|
||||||
|
testShortOldForkedFastSyncingSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestShortOldForkedFastSyncingSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testShortOldForkedFastSyncingSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShortOldForkedFastSyncingSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -388,7 +415,7 @@ func TestShortOldForkedFastSyncingSetHead(t *testing.T) {
|
||||||
expHeadHeader: 7,
|
expHeadHeader: 7,
|
||||||
expHeadFastBlock: 7,
|
expHeadFastBlock: 7,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a short canonical chain and a shorter side chain, where a
|
// Tests a sethead for a short canonical chain and a shorter side chain, where a
|
||||||
|
|
@ -402,7 +429,10 @@ func TestShortOldForkedFastSyncingSetHead(t *testing.T) {
|
||||||
// The side chain could be left to be if the fork point was before the new head
|
// The side chain could be left to be if the fork point was before the new head
|
||||||
// we are deleting to, but it would be exceedingly hard to detect that case and
|
// we are deleting to, but it would be exceedingly hard to detect that case and
|
||||||
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
||||||
func TestShortNewlyForkedSetHead(t *testing.T) {
|
func TestShortNewlyForkedSetHead(t *testing.T) { testShortNewlyForkedSetHead(t, false) }
|
||||||
|
func TestShortNewlyForkedSetHeadWithSnapshots(t *testing.T) { testShortNewlyForkedSetHead(t, true) }
|
||||||
|
|
||||||
|
func testShortNewlyForkedSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8
|
// └->S1->S2->S3->S4->S5->S6->S7->S8
|
||||||
|
|
@ -435,7 +465,7 @@ func TestShortNewlyForkedSetHead(t *testing.T) {
|
||||||
expHeadHeader: 7,
|
expHeadHeader: 7,
|
||||||
expHeadFastBlock: 7,
|
expHeadFastBlock: 7,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a short canonical chain and a shorter side chain, where
|
// Tests a sethead for a short canonical chain and a shorter side chain, where
|
||||||
|
|
@ -449,6 +479,13 @@ func TestShortNewlyForkedSetHead(t *testing.T) {
|
||||||
// we are deleting to, but it would be exceedingly hard to detect that case and
|
// we are deleting to, but it would be exceedingly hard to detect that case and
|
||||||
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
||||||
func TestShortNewlyForkedFastSyncedSetHead(t *testing.T) {
|
func TestShortNewlyForkedFastSyncedSetHead(t *testing.T) {
|
||||||
|
testShortNewlyForkedFastSyncedSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestShortNewlyForkedFastSyncedSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testShortNewlyForkedFastSyncedSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShortNewlyForkedFastSyncedSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8
|
// └->S1->S2->S3->S4->S5->S6->S7->S8
|
||||||
|
|
@ -481,7 +518,7 @@ func TestShortNewlyForkedFastSyncedSetHead(t *testing.T) {
|
||||||
expHeadHeader: 7,
|
expHeadHeader: 7,
|
||||||
expHeadFastBlock: 7,
|
expHeadFastBlock: 7,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a short canonical chain and a shorter side chain, where
|
// Tests a sethead for a short canonical chain and a shorter side chain, where
|
||||||
|
|
@ -495,6 +532,13 @@ func TestShortNewlyForkedFastSyncedSetHead(t *testing.T) {
|
||||||
// we are deleting to, but it would be exceedingly hard to detect that case and
|
// we are deleting to, but it would be exceedingly hard to detect that case and
|
||||||
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
||||||
func TestShortNewlyForkedFastSyncingSetHead(t *testing.T) {
|
func TestShortNewlyForkedFastSyncingSetHead(t *testing.T) {
|
||||||
|
testShortNewlyForkedFastSyncingSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestShortNewlyForkedFastSyncingSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testShortNewlyForkedFastSyncingSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShortNewlyForkedFastSyncingSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8
|
// └->S1->S2->S3->S4->S5->S6->S7->S8
|
||||||
|
|
@ -527,7 +571,7 @@ func TestShortNewlyForkedFastSyncingSetHead(t *testing.T) {
|
||||||
expHeadHeader: 7,
|
expHeadHeader: 7,
|
||||||
expHeadFastBlock: 7,
|
expHeadFastBlock: 7,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a short canonical chain and a longer side chain, where a
|
// Tests a sethead for a short canonical chain and a longer side chain, where a
|
||||||
|
|
@ -540,7 +584,10 @@ func TestShortNewlyForkedFastSyncingSetHead(t *testing.T) {
|
||||||
// The side chain could be left to be if the fork point was before the new head
|
// The side chain could be left to be if the fork point was before the new head
|
||||||
// we are deleting to, but it would be exceedingly hard to detect that case and
|
// we are deleting to, but it would be exceedingly hard to detect that case and
|
||||||
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
||||||
func TestShortReorgedSetHead(t *testing.T) {
|
func TestShortReorgedSetHead(t *testing.T) { testShortReorgedSetHead(t, false) }
|
||||||
|
func TestShortReorgedSetHeadWithSnapshots(t *testing.T) { testShortReorgedSetHead(t, true) }
|
||||||
|
|
||||||
|
func testShortReorgedSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
||||||
|
|
@ -573,7 +620,7 @@ func TestShortReorgedSetHead(t *testing.T) {
|
||||||
expHeadHeader: 7,
|
expHeadHeader: 7,
|
||||||
expHeadFastBlock: 7,
|
expHeadFastBlock: 7,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a short canonical chain and a longer side chain, where
|
// Tests a sethead for a short canonical chain and a longer side chain, where
|
||||||
|
|
@ -588,6 +635,13 @@ func TestShortReorgedSetHead(t *testing.T) {
|
||||||
// we are deleting to, but it would be exceedingly hard to detect that case and
|
// we are deleting to, but it would be exceedingly hard to detect that case and
|
||||||
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
||||||
func TestShortReorgedFastSyncedSetHead(t *testing.T) {
|
func TestShortReorgedFastSyncedSetHead(t *testing.T) {
|
||||||
|
testShortReorgedFastSyncedSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestShortReorgedFastSyncedSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testShortReorgedFastSyncedSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShortReorgedFastSyncedSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
||||||
|
|
@ -620,7 +674,7 @@ func TestShortReorgedFastSyncedSetHead(t *testing.T) {
|
||||||
expHeadHeader: 7,
|
expHeadHeader: 7,
|
||||||
expHeadFastBlock: 7,
|
expHeadFastBlock: 7,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a short canonical chain and a longer side chain, where
|
// Tests a sethead for a short canonical chain and a longer side chain, where
|
||||||
|
|
@ -633,6 +687,13 @@ func TestShortReorgedFastSyncedSetHead(t *testing.T) {
|
||||||
// we are deleting to, but it would be exceedingly hard to detect that case and
|
// we are deleting to, but it would be exceedingly hard to detect that case and
|
||||||
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
||||||
func TestShortReorgedFastSyncingSetHead(t *testing.T) {
|
func TestShortReorgedFastSyncingSetHead(t *testing.T) {
|
||||||
|
testShortReorgedFastSyncingSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestShortReorgedFastSyncingSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testShortReorgedFastSyncingSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShortReorgedFastSyncingSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
||||||
|
|
@ -665,7 +726,7 @@ func TestShortReorgedFastSyncingSetHead(t *testing.T) {
|
||||||
expHeadHeader: 7,
|
expHeadHeader: 7,
|
||||||
expHeadFastBlock: 7,
|
expHeadFastBlock: 7,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks where a recent
|
// Tests a sethead for a long canonical chain with frozen blocks where a recent
|
||||||
|
|
@ -674,7 +735,10 @@ func TestShortReorgedFastSyncingSetHead(t *testing.T) {
|
||||||
// to the committed block. Everything above the sethead point should be deleted.
|
// to the committed block. Everything above the sethead point should be deleted.
|
||||||
// In between the committed block and the requested head the data can remain as
|
// In between the committed block and the requested head the data can remain as
|
||||||
// "fast sync" data to avoid redownloading it.
|
// "fast sync" data to avoid redownloading it.
|
||||||
func TestLongShallowSetHead(t *testing.T) {
|
func TestLongShallowSetHead(t *testing.T) { testLongShallowSetHead(t, false) }
|
||||||
|
func TestLongShallowSetHeadWithSnapshots(t *testing.T) { testLongShallowSetHead(t, true) }
|
||||||
|
|
||||||
|
func testLongShallowSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -710,7 +774,7 @@ func TestLongShallowSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks where a recent
|
// Tests a sethead for a long canonical chain with frozen blocks where a recent
|
||||||
|
|
@ -718,7 +782,10 @@ func TestLongShallowSetHead(t *testing.T) {
|
||||||
// sethead was called. In this case we expect the full chain to be rolled back
|
// sethead was called. In this case we expect the full chain to be rolled back
|
||||||
// to the committed block. Since the ancient limit was underflown, everything
|
// to the committed block. Since the ancient limit was underflown, everything
|
||||||
// needs to be deleted onwards to avoid creating a gap.
|
// needs to be deleted onwards to avoid creating a gap.
|
||||||
func TestLongDeepSetHead(t *testing.T) {
|
func TestLongDeepSetHead(t *testing.T) { testLongDeepSetHead(t, false) }
|
||||||
|
func TestLongDeepSetHeadWithSnapshots(t *testing.T) { testLongDeepSetHead(t, true) }
|
||||||
|
|
||||||
|
func testLongDeepSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -753,7 +820,7 @@ func TestLongDeepSetHead(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks where the fast
|
// Tests a sethead for a long canonical chain with frozen blocks where the fast
|
||||||
|
|
@ -763,6 +830,13 @@ func TestLongDeepSetHead(t *testing.T) {
|
||||||
// deleted. In between the committed block and the requested head the data can
|
// deleted. In between the committed block and the requested head the data can
|
||||||
// remain as "fast sync" data to avoid redownloading it.
|
// remain as "fast sync" data to avoid redownloading it.
|
||||||
func TestLongFastSyncedShallowSetHead(t *testing.T) {
|
func TestLongFastSyncedShallowSetHead(t *testing.T) {
|
||||||
|
testLongFastSyncedShallowSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongFastSyncedShallowSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongFastSyncedShallowSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -798,7 +872,7 @@ func TestLongFastSyncedShallowSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks where the fast
|
// Tests a sethead for a long canonical chain with frozen blocks where the fast
|
||||||
|
|
@ -806,7 +880,10 @@ func TestLongFastSyncedShallowSetHead(t *testing.T) {
|
||||||
// which sethead was called. In this case we expect the full chain to be rolled
|
// which sethead was called. In this case we expect the full chain to be rolled
|
||||||
// back to the committed block. Since the ancient limit was underflown, everything
|
// back to the committed block. Since the ancient limit was underflown, everything
|
||||||
// needs to be deleted onwards to avoid creating a gap.
|
// needs to be deleted onwards to avoid creating a gap.
|
||||||
func TestLongFastSyncedDeepSetHead(t *testing.T) {
|
func TestLongFastSyncedDeepSetHead(t *testing.T) { testLongFastSyncedDeepSetHead(t, false) }
|
||||||
|
func TestLongFastSyncedDeepSetHeadWithSnapshots(t *testing.T) { testLongFastSyncedDeepSetHead(t, true) }
|
||||||
|
|
||||||
|
func testLongFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -841,7 +918,7 @@ func TestLongFastSyncedDeepSetHead(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks where the fast
|
// Tests a sethead for a long canonical chain with frozen blocks where the fast
|
||||||
|
|
@ -850,6 +927,13 @@ func TestLongFastSyncedDeepSetHead(t *testing.T) {
|
||||||
// syncing and delete everything from the new head, since we can just pick up fast
|
// syncing and delete everything from the new head, since we can just pick up fast
|
||||||
// syncing from there.
|
// syncing from there.
|
||||||
func TestLongFastSyncingShallowSetHead(t *testing.T) {
|
func TestLongFastSyncingShallowSetHead(t *testing.T) {
|
||||||
|
testLongFastSyncingShallowSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongFastSyncingShallowSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongFastSyncingShallowSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -885,7 +969,7 @@ func TestLongFastSyncingShallowSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks where the fast
|
// Tests a sethead for a long canonical chain with frozen blocks where the fast
|
||||||
|
|
@ -894,6 +978,13 @@ func TestLongFastSyncingShallowSetHead(t *testing.T) {
|
||||||
// syncing and delete everything from the new head, since we can just pick up fast
|
// syncing and delete everything from the new head, since we can just pick up fast
|
||||||
// syncing from there.
|
// syncing from there.
|
||||||
func TestLongFastSyncingDeepSetHead(t *testing.T) {
|
func TestLongFastSyncingDeepSetHead(t *testing.T) {
|
||||||
|
testLongFastSyncingDeepSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongFastSyncingDeepSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongFastSyncingDeepSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongFastSyncingDeepSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
//
|
//
|
||||||
|
|
@ -928,7 +1019,7 @@ func TestLongFastSyncingDeepSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a shorter side
|
// Tests a sethead for a long canonical chain with frozen blocks and a shorter side
|
||||||
|
|
@ -939,6 +1030,13 @@ func TestLongFastSyncingDeepSetHead(t *testing.T) {
|
||||||
// can remain as "fast sync" data to avoid redownloading it. The side chain is nuked
|
// can remain as "fast sync" data to avoid redownloading it. The side chain is nuked
|
||||||
// by the freezer.
|
// by the freezer.
|
||||||
func TestLongOldForkedShallowSetHead(t *testing.T) {
|
func TestLongOldForkedShallowSetHead(t *testing.T) {
|
||||||
|
testLongOldForkedShallowSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongOldForkedShallowSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongOldForkedShallowSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongOldForkedShallowSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -975,7 +1073,7 @@ func TestLongOldForkedShallowSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a shorter side
|
// Tests a sethead for a long canonical chain with frozen blocks and a shorter side
|
||||||
|
|
@ -984,7 +1082,10 @@ func TestLongOldForkedShallowSetHead(t *testing.T) {
|
||||||
// chain to be rolled back to the committed block. Since the ancient limit was
|
// chain to be rolled back to the committed block. Since the ancient limit was
|
||||||
// underflown, everything needs to be deleted onwards to avoid creating a gap. The
|
// underflown, everything needs to be deleted onwards to avoid creating a gap. The
|
||||||
// side chain is nuked by the freezer.
|
// side chain is nuked by the freezer.
|
||||||
func TestLongOldForkedDeepSetHead(t *testing.T) {
|
func TestLongOldForkedDeepSetHead(t *testing.T) { testLongOldForkedDeepSetHead(t, false) }
|
||||||
|
func TestLongOldForkedDeepSetHeadWithSnapshots(t *testing.T) { testLongOldForkedDeepSetHead(t, true) }
|
||||||
|
|
||||||
|
func testLongOldForkedDeepSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -1020,7 +1121,7 @@ func TestLongOldForkedDeepSetHead(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1032,6 +1133,13 @@ func TestLongOldForkedDeepSetHead(t *testing.T) {
|
||||||
// requested head the data can remain as "fast sync" data to avoid redownloading
|
// requested head the data can remain as "fast sync" data to avoid redownloading
|
||||||
// it. The side chain is nuked by the freezer.
|
// it. The side chain is nuked by the freezer.
|
||||||
func TestLongOldForkedFastSyncedShallowSetHead(t *testing.T) {
|
func TestLongOldForkedFastSyncedShallowSetHead(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncedShallowSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongOldForkedFastSyncedShallowSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncedShallowSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongOldForkedFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -1068,7 +1176,7 @@ func TestLongOldForkedFastSyncedShallowSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1079,6 +1187,13 @@ func TestLongOldForkedFastSyncedShallowSetHead(t *testing.T) {
|
||||||
// underflown, everything needs to be deleted onwards to avoid creating a gap. The
|
// underflown, everything needs to be deleted onwards to avoid creating a gap. The
|
||||||
// side chain is nuked by the freezer.
|
// side chain is nuked by the freezer.
|
||||||
func TestLongOldForkedFastSyncedDeepSetHead(t *testing.T) {
|
func TestLongOldForkedFastSyncedDeepSetHead(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncedDeepSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongOldForkedFastSyncedDeepSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncedDeepSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongOldForkedFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -1114,7 +1229,7 @@ func TestLongOldForkedFastSyncedDeepSetHead(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1125,6 +1240,13 @@ func TestLongOldForkedFastSyncedDeepSetHead(t *testing.T) {
|
||||||
// just pick up fast syncing from there. The side chain is completely nuked by the
|
// just pick up fast syncing from there. The side chain is completely nuked by the
|
||||||
// freezer.
|
// freezer.
|
||||||
func TestLongOldForkedFastSyncingShallowSetHead(t *testing.T) {
|
func TestLongOldForkedFastSyncingShallowSetHead(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncingShallowSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongOldForkedFastSyncingShallowSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncingShallowSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongOldForkedFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -1161,7 +1283,7 @@ func TestLongOldForkedFastSyncingShallowSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1172,6 +1294,13 @@ func TestLongOldForkedFastSyncingShallowSetHead(t *testing.T) {
|
||||||
// just pick up fast syncing from there. The side chain is completely nuked by the
|
// just pick up fast syncing from there. The side chain is completely nuked by the
|
||||||
// freezer.
|
// freezer.
|
||||||
func TestLongOldForkedFastSyncingDeepSetHead(t *testing.T) {
|
func TestLongOldForkedFastSyncingDeepSetHead(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncingDeepSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongOldForkedFastSyncingDeepSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongOldForkedFastSyncingDeepSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongOldForkedFastSyncingDeepSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3
|
// └->S1->S2->S3
|
||||||
|
|
@ -1207,7 +1336,7 @@ func TestLongOldForkedFastSyncingDeepSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1216,6 +1345,13 @@ func TestLongOldForkedFastSyncingDeepSetHead(t *testing.T) {
|
||||||
// chain is above the committed block. In this case the freezer will delete the
|
// chain is above the committed block. In this case the freezer will delete the
|
||||||
// sidechain since it's dangling, reverting to TestLongShallowSetHead.
|
// sidechain since it's dangling, reverting to TestLongShallowSetHead.
|
||||||
func TestLongNewerForkedShallowSetHead(t *testing.T) {
|
func TestLongNewerForkedShallowSetHead(t *testing.T) {
|
||||||
|
testLongNewerForkedShallowSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongNewerForkedShallowSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongNewerForkedShallowSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongNewerForkedShallowSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||||
|
|
@ -1252,7 +1388,7 @@ func TestLongNewerForkedShallowSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1261,6 +1397,13 @@ func TestLongNewerForkedShallowSetHead(t *testing.T) {
|
||||||
// chain is above the committed block. In this case the freezer will delete the
|
// chain is above the committed block. In this case the freezer will delete the
|
||||||
// sidechain since it's dangling, reverting to TestLongDeepSetHead.
|
// sidechain since it's dangling, reverting to TestLongDeepSetHead.
|
||||||
func TestLongNewerForkedDeepSetHead(t *testing.T) {
|
func TestLongNewerForkedDeepSetHead(t *testing.T) {
|
||||||
|
testLongNewerForkedDeepSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongNewerForkedDeepSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongNewerForkedDeepSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongNewerForkedDeepSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||||
|
|
@ -1296,7 +1439,7 @@ func TestLongNewerForkedDeepSetHead(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1305,6 +1448,13 @@ func TestLongNewerForkedDeepSetHead(t *testing.T) {
|
||||||
// the side chain is above the committed block. In this case the freezer will delete
|
// the side chain is above the committed block. In this case the freezer will delete
|
||||||
// the sidechain since it's dangling, reverting to TestLongFastSyncedShallowSetHead.
|
// the sidechain since it's dangling, reverting to TestLongFastSyncedShallowSetHead.
|
||||||
func TestLongNewerForkedFastSyncedShallowSetHead(t *testing.T) {
|
func TestLongNewerForkedFastSyncedShallowSetHead(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncedShallowSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongNewerForkedFastSyncedShallowSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncedShallowSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongNewerForkedFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||||
|
|
@ -1341,7 +1491,7 @@ func TestLongNewerForkedFastSyncedShallowSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1350,6 +1500,13 @@ func TestLongNewerForkedFastSyncedShallowSetHead(t *testing.T) {
|
||||||
// the side chain is above the committed block. In this case the freezer will delete
|
// the side chain is above the committed block. In this case the freezer will delete
|
||||||
// the sidechain since it's dangling, reverting to TestLongFastSyncedDeepSetHead.
|
// the sidechain since it's dangling, reverting to TestLongFastSyncedDeepSetHead.
|
||||||
func TestLongNewerForkedFastSyncedDeepSetHead(t *testing.T) {
|
func TestLongNewerForkedFastSyncedDeepSetHead(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncedDeepSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongNewerForkedFastSyncedDeepSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncedDeepSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongNewerForkedFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||||
|
|
@ -1385,7 +1542,7 @@ func TestLongNewerForkedFastSyncedDeepSetHead(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1394,6 +1551,13 @@ func TestLongNewerForkedFastSyncedDeepSetHead(t *testing.T) {
|
||||||
// chain is above the committed block. In this case the freezer will delete the
|
// chain is above the committed block. In this case the freezer will delete the
|
||||||
// sidechain since it's dangling, reverting to TestLongFastSyncinghallowSetHead.
|
// sidechain since it's dangling, reverting to TestLongFastSyncinghallowSetHead.
|
||||||
func TestLongNewerForkedFastSyncingShallowSetHead(t *testing.T) {
|
func TestLongNewerForkedFastSyncingShallowSetHead(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncingShallowSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongNewerForkedFastSyncingShallowSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncingShallowSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongNewerForkedFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||||
|
|
@ -1430,7 +1594,7 @@ func TestLongNewerForkedFastSyncingShallowSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
// Tests a sethead for a long canonical chain with frozen blocks and a shorter
|
||||||
|
|
@ -1439,6 +1603,13 @@ func TestLongNewerForkedFastSyncingShallowSetHead(t *testing.T) {
|
||||||
// chain is above the committed block. In this case the freezer will delete the
|
// chain is above the committed block. In this case the freezer will delete the
|
||||||
// sidechain since it's dangling, reverting to TestLongFastSyncingDeepSetHead.
|
// sidechain since it's dangling, reverting to TestLongFastSyncingDeepSetHead.
|
||||||
func TestLongNewerForkedFastSyncingDeepSetHead(t *testing.T) {
|
func TestLongNewerForkedFastSyncingDeepSetHead(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncingDeepSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongNewerForkedFastSyncingDeepSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongNewerForkedFastSyncingDeepSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongNewerForkedFastSyncingDeepSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||||
|
|
@ -1474,14 +1645,17 @@ func TestLongNewerForkedFastSyncingDeepSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a longer side
|
// Tests a sethead for a long canonical chain with frozen blocks and a longer side
|
||||||
// chain, where a recent block - newer than the ancient limit - was already committed
|
// chain, where a recent block - newer than the ancient limit - was already committed
|
||||||
// to disk and then sethead was called. In this case the freezer will delete the
|
// to disk and then sethead was called. In this case the freezer will delete the
|
||||||
// sidechain since it's dangling, reverting to TestLongShallowSetHead.
|
// sidechain since it's dangling, reverting to TestLongShallowSetHead.
|
||||||
func TestLongReorgedShallowSetHead(t *testing.T) {
|
func TestLongReorgedShallowSetHead(t *testing.T) { testLongReorgedShallowSetHead(t, false) }
|
||||||
|
func TestLongReorgedShallowSetHeadWithSnapshots(t *testing.T) { testLongReorgedShallowSetHead(t, true) }
|
||||||
|
|
||||||
|
func testLongReorgedShallowSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||||
|
|
@ -1518,14 +1692,17 @@ func TestLongReorgedShallowSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a longer side
|
// Tests a sethead for a long canonical chain with frozen blocks and a longer side
|
||||||
// chain, where a recent block - older than the ancient limit - was already committed
|
// chain, where a recent block - older than the ancient limit - was already committed
|
||||||
// to disk and then sethead was called. In this case the freezer will delete the
|
// to disk and then sethead was called. In this case the freezer will delete the
|
||||||
// sidechain since it's dangling, reverting to TestLongDeepSetHead.
|
// sidechain since it's dangling, reverting to TestLongDeepSetHead.
|
||||||
func TestLongReorgedDeepSetHead(t *testing.T) {
|
func TestLongReorgedDeepSetHead(t *testing.T) { testLongReorgedDeepSetHead(t, false) }
|
||||||
|
func TestLongReorgedDeepSetHeadWithSnapshots(t *testing.T) { testLongReorgedDeepSetHead(t, true) }
|
||||||
|
|
||||||
|
func testLongReorgedDeepSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||||
|
|
@ -1561,7 +1738,7 @@ func TestLongReorgedDeepSetHead(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a longer
|
// Tests a sethead for a long canonical chain with frozen blocks and a longer
|
||||||
|
|
@ -1570,6 +1747,13 @@ func TestLongReorgedDeepSetHead(t *testing.T) {
|
||||||
// freezer will delete the sidechain since it's dangling, reverting to
|
// freezer will delete the sidechain since it's dangling, reverting to
|
||||||
// TestLongFastSyncedShallowSetHead.
|
// TestLongFastSyncedShallowSetHead.
|
||||||
func TestLongReorgedFastSyncedShallowSetHead(t *testing.T) {
|
func TestLongReorgedFastSyncedShallowSetHead(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncedShallowSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongReorgedFastSyncedShallowSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncedShallowSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongReorgedFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||||
|
|
@ -1606,7 +1790,7 @@ func TestLongReorgedFastSyncedShallowSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a longer
|
// Tests a sethead for a long canonical chain with frozen blocks and a longer
|
||||||
|
|
@ -1615,6 +1799,13 @@ func TestLongReorgedFastSyncedShallowSetHead(t *testing.T) {
|
||||||
// freezer will delete the sidechain since it's dangling, reverting to
|
// freezer will delete the sidechain since it's dangling, reverting to
|
||||||
// TestLongFastSyncedDeepSetHead.
|
// TestLongFastSyncedDeepSetHead.
|
||||||
func TestLongReorgedFastSyncedDeepSetHead(t *testing.T) {
|
func TestLongReorgedFastSyncedDeepSetHead(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncedDeepSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongReorgedFastSyncedDeepSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncedDeepSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongReorgedFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||||
|
|
@ -1650,7 +1841,7 @@ func TestLongReorgedFastSyncedDeepSetHead(t *testing.T) {
|
||||||
expHeadHeader: 4,
|
expHeadHeader: 4,
|
||||||
expHeadFastBlock: 4,
|
expHeadFastBlock: 4,
|
||||||
expHeadBlock: 4,
|
expHeadBlock: 4,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a longer
|
// Tests a sethead for a long canonical chain with frozen blocks and a longer
|
||||||
|
|
@ -1660,6 +1851,13 @@ func TestLongReorgedFastSyncedDeepSetHead(t *testing.T) {
|
||||||
// head, since we can just pick up fast syncing from there. The side chain is
|
// head, since we can just pick up fast syncing from there. The side chain is
|
||||||
// completely nuked by the freezer.
|
// completely nuked by the freezer.
|
||||||
func TestLongReorgedFastSyncingShallowSetHead(t *testing.T) {
|
func TestLongReorgedFastSyncingShallowSetHead(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncingShallowSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongReorgedFastSyncingShallowSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncingShallowSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongReorgedFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||||
|
|
@ -1696,7 +1894,7 @@ func TestLongReorgedFastSyncingShallowSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests a sethead for a long canonical chain with frozen blocks and a longer
|
// Tests a sethead for a long canonical chain with frozen blocks and a longer
|
||||||
|
|
@ -1706,6 +1904,13 @@ func TestLongReorgedFastSyncingShallowSetHead(t *testing.T) {
|
||||||
// head, since we can just pick up fast syncing from there. The side chain is
|
// head, since we can just pick up fast syncing from there. The side chain is
|
||||||
// completely nuked by the freezer.
|
// completely nuked by the freezer.
|
||||||
func TestLongReorgedFastSyncingDeepSetHead(t *testing.T) {
|
func TestLongReorgedFastSyncingDeepSetHead(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncingDeepSetHead(t, false)
|
||||||
|
}
|
||||||
|
func TestLongReorgedFastSyncingDeepSetHeadWithSnapshots(t *testing.T) {
|
||||||
|
testLongReorgedFastSyncingDeepSetHead(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongReorgedFastSyncingDeepSetHead(t *testing.T, snapshots bool) {
|
||||||
// Chain:
|
// Chain:
|
||||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||||
|
|
@ -1741,13 +1946,13 @@ func TestLongReorgedFastSyncingDeepSetHead(t *testing.T) {
|
||||||
expHeadHeader: 6,
|
expHeadHeader: 6,
|
||||||
expHeadFastBlock: 6,
|
expHeadFastBlock: 6,
|
||||||
expHeadBlock: 0,
|
expHeadBlock: 0,
|
||||||
})
|
}, snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSetHead(t *testing.T, tt *rewindTest) {
|
func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) {
|
||||||
// It's hard to follow the test case, visualize the input
|
// It's hard to follow the test case, visualize the input
|
||||||
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
||||||
//fmt.Println(tt.dump(false))
|
// fmt.Println(tt.dump(false))
|
||||||
|
|
||||||
// Create a temporary persistent database
|
// Create a temporary persistent database
|
||||||
datadir, err := ioutil.TempDir("", "")
|
datadir, err := ioutil.TempDir("", "")
|
||||||
|
|
@ -1766,8 +1971,18 @@ func testSetHead(t *testing.T, tt *rewindTest) {
|
||||||
var (
|
var (
|
||||||
genesis = new(Genesis).MustCommit(db)
|
genesis = new(Genesis).MustCommit(db)
|
||||||
engine = ethash.NewFullFaker()
|
engine = ethash.NewFullFaker()
|
||||||
|
config = &CacheConfig{
|
||||||
|
TrieCleanLimit: 256,
|
||||||
|
TrieDirtyLimit: 256,
|
||||||
|
TrieTimeLimit: 5 * time.Minute,
|
||||||
|
SnapshotLimit: 0, // Disable snapshot
|
||||||
|
}
|
||||||
)
|
)
|
||||||
chain, err := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
if snapshots {
|
||||||
|
config.SnapshotLimit = 256
|
||||||
|
config.SnapshotWait = true
|
||||||
|
}
|
||||||
|
chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create chain: %v", err)
|
t.Fatalf("Failed to create chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1790,6 +2005,11 @@ func testSetHead(t *testing.T, tt *rewindTest) {
|
||||||
}
|
}
|
||||||
if tt.commitBlock > 0 {
|
if tt.commitBlock > 0 {
|
||||||
chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil)
|
chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil)
|
||||||
|
if snapshots {
|
||||||
|
if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil {
|
||||||
|
t.Fatalf("Failed to flatten snapshots: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if _, err := chain.InsertChain(canonblocks[tt.commitBlock:]); err != nil {
|
if _, err := chain.InsertChain(canonblocks[tt.commitBlock:]); err != nil {
|
||||||
t.Fatalf("Failed to import canonical chain tail: %v", err)
|
t.Fatalf("Failed to import canonical chain tail: %v", err)
|
||||||
|
|
|
||||||
797
core/blockchain_snapshot_test.go
Normal file
797
core/blockchain_snapshot_test.go
Normal file
|
|
@ -0,0 +1,797 @@
|
||||||
|
// Copyright 2020 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/>.
|
||||||
|
|
||||||
|
// Tests that abnormal program termination (i.e.crash) and restart can recovery
|
||||||
|
// the snapshot properly if the snapshot is enabled.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
// snapshotTest is a test case for snapshot recovery. It can be used for
|
||||||
|
// simulating these scenarios:
|
||||||
|
// (i) Geth restarts normally with valid legacy snapshot
|
||||||
|
// (ii) Geth restarts normally with valid new-format snapshot
|
||||||
|
// (iii) Geth restarts after the crash, with broken legacy snapshot
|
||||||
|
// (iv) Geth restarts after the crash, with broken new-format snapshot
|
||||||
|
// (v) Geth restarts normally, but it's requested to be rewound to a lower point via SetHead
|
||||||
|
// (vi) Geth restarts normally with a stale snapshot
|
||||||
|
type snapshotTest struct {
|
||||||
|
legacy bool // Flag whether the loaded snapshot is in legacy format
|
||||||
|
crash bool // Flag whether the Geth restarts from the previous crash
|
||||||
|
restartCrash int // Number of blocks to insert after the normal stop, then the crash happens
|
||||||
|
gapped int // Number of blocks to insert without enabling snapshot
|
||||||
|
setHead uint64 // Block number to set head back to
|
||||||
|
|
||||||
|
chainBlocks int // Number of blocks to generate for the canonical chain
|
||||||
|
snapshotBlock uint64 // Block number of the relevant snapshot disk layer
|
||||||
|
commitBlock uint64 // Block number for which to commit the state to disk
|
||||||
|
|
||||||
|
expCanonicalBlocks int // Number of canonical blocks expected to remain in the database (excl. genesis)
|
||||||
|
expHeadHeader uint64 // Block number of the expected head header
|
||||||
|
expHeadFastBlock uint64 // Block number of the expected head fast sync block
|
||||||
|
expHeadBlock uint64 // Block number of the expected head full block
|
||||||
|
expSnapshotBottom uint64 // The block height corresponding to the snapshot disk layer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tt *snapshotTest) dump() string {
|
||||||
|
buffer := new(strings.Builder)
|
||||||
|
|
||||||
|
fmt.Fprint(buffer, "Chain:\n G")
|
||||||
|
for i := 0; i < tt.chainBlocks; i++ {
|
||||||
|
fmt.Fprintf(buffer, "->C%d", i+1)
|
||||||
|
}
|
||||||
|
fmt.Fprint(buffer, " (HEAD)\n\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(buffer, "Commit: G")
|
||||||
|
if tt.commitBlock > 0 {
|
||||||
|
fmt.Fprintf(buffer, ", C%d", tt.commitBlock)
|
||||||
|
}
|
||||||
|
fmt.Fprint(buffer, "\n")
|
||||||
|
|
||||||
|
fmt.Fprintf(buffer, "Snapshot: G")
|
||||||
|
if tt.snapshotBlock > 0 {
|
||||||
|
fmt.Fprintf(buffer, ", C%d", tt.snapshotBlock)
|
||||||
|
}
|
||||||
|
fmt.Fprint(buffer, "\n")
|
||||||
|
|
||||||
|
if tt.crash {
|
||||||
|
fmt.Fprintf(buffer, "\nCRASH\n\n")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(buffer, "\nSetHead(%d)\n\n", tt.setHead)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(buffer, "------------------------------\n\n")
|
||||||
|
|
||||||
|
fmt.Fprint(buffer, "Expected in leveldb:\n G")
|
||||||
|
for i := 0; i < tt.expCanonicalBlocks; i++ {
|
||||||
|
fmt.Fprintf(buffer, "->C%d", i+1)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(buffer, "\n\n")
|
||||||
|
fmt.Fprintf(buffer, "Expected head header : C%d\n", tt.expHeadHeader)
|
||||||
|
fmt.Fprintf(buffer, "Expected head fast block: C%d\n", tt.expHeadFastBlock)
|
||||||
|
if tt.expHeadBlock == 0 {
|
||||||
|
fmt.Fprintf(buffer, "Expected head block : G\n")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(buffer, "Expected head block : C%d\n", tt.expHeadBlock)
|
||||||
|
}
|
||||||
|
if tt.expSnapshotBottom == 0 {
|
||||||
|
fmt.Fprintf(buffer, "Expected snapshot disk : G\n")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(buffer, "Expected snapshot disk : C%d\n", tt.expSnapshotBottom)
|
||||||
|
}
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests a Geth restart with valid snapshot. Before the shutdown, all snapshot
|
||||||
|
// journal will be persisted correctly. In this case no snapshot recovery is
|
||||||
|
// required.
|
||||||
|
func TestRestartWithNewSnapshot(t *testing.T) {
|
||||||
|
// Chain:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
|
//
|
||||||
|
// Commit: G
|
||||||
|
// Snapshot: G
|
||||||
|
//
|
||||||
|
// SetHead(0)
|
||||||
|
//
|
||||||
|
// ------------------------------
|
||||||
|
//
|
||||||
|
// Expected in leveldb:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||||
|
//
|
||||||
|
// Expected head header : C8
|
||||||
|
// Expected head fast block: C8
|
||||||
|
// Expected head block : C8
|
||||||
|
// Expected snapshot disk : G
|
||||||
|
testSnapshot(t, &snapshotTest{
|
||||||
|
legacy: false,
|
||||||
|
crash: false,
|
||||||
|
gapped: 0,
|
||||||
|
setHead: 0,
|
||||||
|
chainBlocks: 8,
|
||||||
|
snapshotBlock: 0,
|
||||||
|
commitBlock: 0,
|
||||||
|
expCanonicalBlocks: 8,
|
||||||
|
expHeadHeader: 8,
|
||||||
|
expHeadFastBlock: 8,
|
||||||
|
expHeadBlock: 8,
|
||||||
|
expSnapshotBottom: 0, // Initial disk layer built from genesis
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests a Geth restart with valid but "legacy" snapshot. Before the shutdown,
|
||||||
|
// all snapshot journal will be persisted correctly. In this case no snapshot
|
||||||
|
// recovery is required.
|
||||||
|
func TestRestartWithLegacySnapshot(t *testing.T) {
|
||||||
|
// Chain:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
|
//
|
||||||
|
// Commit: G
|
||||||
|
// Snapshot: G
|
||||||
|
//
|
||||||
|
// SetHead(0)
|
||||||
|
//
|
||||||
|
// ------------------------------
|
||||||
|
//
|
||||||
|
// Expected in leveldb:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||||
|
//
|
||||||
|
// Expected head header : C8
|
||||||
|
// Expected head fast block: C8
|
||||||
|
// Expected head block : C8
|
||||||
|
// Expected snapshot disk : G
|
||||||
|
testSnapshot(t, &snapshotTest{
|
||||||
|
legacy: true,
|
||||||
|
crash: false,
|
||||||
|
gapped: 0,
|
||||||
|
setHead: 0,
|
||||||
|
chainBlocks: 8,
|
||||||
|
snapshotBlock: 0,
|
||||||
|
commitBlock: 0,
|
||||||
|
expCanonicalBlocks: 8,
|
||||||
|
expHeadHeader: 8,
|
||||||
|
expHeadFastBlock: 8,
|
||||||
|
expHeadBlock: 8,
|
||||||
|
expSnapshotBottom: 0, // Initial disk layer built from genesis
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests a Geth was crashed and restarts with a broken snapshot. In this case the
|
||||||
|
// chain head should be rewound to the point with available state. And also the
|
||||||
|
// new head should must be lower than disk layer. But there is no committed point
|
||||||
|
// so the chain should be rewound to genesis and the disk layer should be left
|
||||||
|
// for recovery.
|
||||||
|
func TestNoCommitCrashWithNewSnapshot(t *testing.T) {
|
||||||
|
// Chain:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
|
//
|
||||||
|
// Commit: G
|
||||||
|
// Snapshot: G, C4
|
||||||
|
//
|
||||||
|
// CRASH
|
||||||
|
//
|
||||||
|
// ------------------------------
|
||||||
|
//
|
||||||
|
// Expected in leveldb:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||||
|
//
|
||||||
|
// Expected head header : C8
|
||||||
|
// Expected head fast block: C8
|
||||||
|
// Expected head block : G
|
||||||
|
// Expected snapshot disk : C4
|
||||||
|
testSnapshot(t, &snapshotTest{
|
||||||
|
legacy: false,
|
||||||
|
crash: true,
|
||||||
|
gapped: 0,
|
||||||
|
setHead: 0,
|
||||||
|
chainBlocks: 8,
|
||||||
|
snapshotBlock: 4,
|
||||||
|
commitBlock: 0,
|
||||||
|
expCanonicalBlocks: 8,
|
||||||
|
expHeadHeader: 8,
|
||||||
|
expHeadFastBlock: 8,
|
||||||
|
expHeadBlock: 0,
|
||||||
|
expSnapshotBottom: 4, // Last committed disk layer, wait recovery
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests a Geth was crashed and restarts with a broken snapshot. In this case the
|
||||||
|
// chain head should be rewound to the point with available state. And also the
|
||||||
|
// new head should must be lower than disk layer. But there is only a low committed
|
||||||
|
// point so the chain should be rewound to committed point and the disk layer
|
||||||
|
// should be left for recovery.
|
||||||
|
func TestLowCommitCrashWithNewSnapshot(t *testing.T) {
|
||||||
|
// Chain:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
|
//
|
||||||
|
// Commit: G, C2
|
||||||
|
// Snapshot: G, C4
|
||||||
|
//
|
||||||
|
// CRASH
|
||||||
|
//
|
||||||
|
// ------------------------------
|
||||||
|
//
|
||||||
|
// Expected in leveldb:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||||
|
//
|
||||||
|
// Expected head header : C8
|
||||||
|
// Expected head fast block: C8
|
||||||
|
// Expected head block : C2
|
||||||
|
// Expected snapshot disk : C4
|
||||||
|
testSnapshot(t, &snapshotTest{
|
||||||
|
legacy: false,
|
||||||
|
crash: true,
|
||||||
|
gapped: 0,
|
||||||
|
setHead: 0,
|
||||||
|
chainBlocks: 8,
|
||||||
|
snapshotBlock: 4,
|
||||||
|
commitBlock: 2,
|
||||||
|
expCanonicalBlocks: 8,
|
||||||
|
expHeadHeader: 8,
|
||||||
|
expHeadFastBlock: 8,
|
||||||
|
expHeadBlock: 2,
|
||||||
|
expSnapshotBottom: 4, // Last committed disk layer, wait recovery
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests a Geth was crashed and restarts with a broken snapshot. In this case
|
||||||
|
// the chain head should be rewound to the point with available state. And also
|
||||||
|
// the new head should must be lower than disk layer. But there is only a high
|
||||||
|
// committed point so the chain should be rewound to genesis and the disk layer
|
||||||
|
// should be left for recovery.
|
||||||
|
func TestHighCommitCrashWithNewSnapshot(t *testing.T) {
|
||||||
|
// Chain:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
|
//
|
||||||
|
// Commit: G, C6
|
||||||
|
// Snapshot: G, C4
|
||||||
|
//
|
||||||
|
// CRASH
|
||||||
|
//
|
||||||
|
// ------------------------------
|
||||||
|
//
|
||||||
|
// Expected in leveldb:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||||
|
//
|
||||||
|
// Expected head header : C8
|
||||||
|
// Expected head fast block: C8
|
||||||
|
// Expected head block : G
|
||||||
|
// Expected snapshot disk : C4
|
||||||
|
testSnapshot(t, &snapshotTest{
|
||||||
|
legacy: false,
|
||||||
|
crash: true,
|
||||||
|
gapped: 0,
|
||||||
|
setHead: 0,
|
||||||
|
chainBlocks: 8,
|
||||||
|
snapshotBlock: 4,
|
||||||
|
commitBlock: 6,
|
||||||
|
expCanonicalBlocks: 8,
|
||||||
|
expHeadHeader: 8,
|
||||||
|
expHeadFastBlock: 8,
|
||||||
|
expHeadBlock: 0,
|
||||||
|
expSnapshotBottom: 4, // Last committed disk layer, wait recovery
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests a Geth was crashed and restarts with a broken and "legacy format"
|
||||||
|
// snapshot. In this case the entire legacy snapshot should be discared
|
||||||
|
// and rebuild from the new chain head. The new head here refers to the
|
||||||
|
// genesis because there is no committed point.
|
||||||
|
func TestNoCommitCrashWithLegacySnapshot(t *testing.T) {
|
||||||
|
// Chain:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
|
//
|
||||||
|
// Commit: G
|
||||||
|
// Snapshot: G, C4
|
||||||
|
//
|
||||||
|
// CRASH
|
||||||
|
//
|
||||||
|
// ------------------------------
|
||||||
|
//
|
||||||
|
// Expected in leveldb:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||||
|
//
|
||||||
|
// Expected head header : C8
|
||||||
|
// Expected head fast block: C8
|
||||||
|
// Expected head block : G
|
||||||
|
// Expected snapshot disk : G
|
||||||
|
testSnapshot(t, &snapshotTest{
|
||||||
|
legacy: true,
|
||||||
|
crash: true,
|
||||||
|
gapped: 0,
|
||||||
|
setHead: 0,
|
||||||
|
chainBlocks: 8,
|
||||||
|
snapshotBlock: 4,
|
||||||
|
commitBlock: 0,
|
||||||
|
expCanonicalBlocks: 8,
|
||||||
|
expHeadHeader: 8,
|
||||||
|
expHeadFastBlock: 8,
|
||||||
|
expHeadBlock: 0,
|
||||||
|
expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD(genesis)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests a Geth was crashed and restarts with a broken and "legacy format"
|
||||||
|
// snapshot. In this case the entire legacy snapshot should be discared
|
||||||
|
// and rebuild from the new chain head. The new head here refers to the
|
||||||
|
// block-2 because it's committed into the disk.
|
||||||
|
func TestLowCommitCrashWithLegacySnapshot(t *testing.T) {
|
||||||
|
// Chain:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
|
//
|
||||||
|
// Commit: G, C2
|
||||||
|
// Snapshot: G, C4
|
||||||
|
//
|
||||||
|
// CRASH
|
||||||
|
//
|
||||||
|
// ------------------------------
|
||||||
|
//
|
||||||
|
// Expected in leveldb:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||||
|
//
|
||||||
|
// Expected head header : C8
|
||||||
|
// Expected head fast block: C8
|
||||||
|
// Expected head block : C2
|
||||||
|
// Expected snapshot disk : C2
|
||||||
|
testSnapshot(t, &snapshotTest{
|
||||||
|
legacy: true,
|
||||||
|
crash: true,
|
||||||
|
gapped: 0,
|
||||||
|
setHead: 0,
|
||||||
|
chainBlocks: 8,
|
||||||
|
snapshotBlock: 4,
|
||||||
|
commitBlock: 2,
|
||||||
|
expCanonicalBlocks: 8,
|
||||||
|
expHeadHeader: 8,
|
||||||
|
expHeadFastBlock: 8,
|
||||||
|
expHeadBlock: 2,
|
||||||
|
expSnapshotBottom: 2, // Rebuilt snapshot from the latest HEAD
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests a Geth was crashed and restarts with a broken and "legacy format"
|
||||||
|
// snapshot. In this case the entire legacy snapshot should be discared
|
||||||
|
// and rebuild from the new chain head.
|
||||||
|
//
|
||||||
|
// The new head here refers to the the genesis, the reason is:
|
||||||
|
// - the state of block-6 is committed into the disk
|
||||||
|
// - the legacy disk layer of block-4 is committed into the disk
|
||||||
|
// - the head is rewound the genesis in order to find an available
|
||||||
|
// state lower than disk layer
|
||||||
|
func TestHighCommitCrashWithLegacySnapshot(t *testing.T) {
|
||||||
|
// Chain:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
|
//
|
||||||
|
// Commit: G, C6
|
||||||
|
// Snapshot: G, C4
|
||||||
|
//
|
||||||
|
// CRASH
|
||||||
|
//
|
||||||
|
// ------------------------------
|
||||||
|
//
|
||||||
|
// Expected in leveldb:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8
|
||||||
|
//
|
||||||
|
// Expected head header : C8
|
||||||
|
// Expected head fast block: C8
|
||||||
|
// Expected head block : G
|
||||||
|
// Expected snapshot disk : G
|
||||||
|
testSnapshot(t, &snapshotTest{
|
||||||
|
legacy: true,
|
||||||
|
crash: true,
|
||||||
|
gapped: 0,
|
||||||
|
setHead: 0,
|
||||||
|
chainBlocks: 8,
|
||||||
|
snapshotBlock: 4,
|
||||||
|
commitBlock: 6,
|
||||||
|
expCanonicalBlocks: 8,
|
||||||
|
expHeadHeader: 8,
|
||||||
|
expHeadFastBlock: 8,
|
||||||
|
expHeadBlock: 0,
|
||||||
|
expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD(genesis)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests a Geth was running with snapshot enabled. Then restarts without
|
||||||
|
// enabling snapshot and after that re-enable the snapshot again. In this
|
||||||
|
// case the snapshot should be rebuilt with latest chain head.
|
||||||
|
func TestGappedNewSnapshot(t *testing.T) {
|
||||||
|
// Chain:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
|
//
|
||||||
|
// Commit: G
|
||||||
|
// Snapshot: G
|
||||||
|
//
|
||||||
|
// SetHead(0)
|
||||||
|
//
|
||||||
|
// ------------------------------
|
||||||
|
//
|
||||||
|
// Expected in leveldb:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10
|
||||||
|
//
|
||||||
|
// Expected head header : C10
|
||||||
|
// Expected head fast block: C10
|
||||||
|
// Expected head block : C10
|
||||||
|
// Expected snapshot disk : C10
|
||||||
|
testSnapshot(t, &snapshotTest{
|
||||||
|
legacy: false,
|
||||||
|
crash: false,
|
||||||
|
gapped: 2,
|
||||||
|
setHead: 0,
|
||||||
|
chainBlocks: 8,
|
||||||
|
snapshotBlock: 0,
|
||||||
|
commitBlock: 0,
|
||||||
|
expCanonicalBlocks: 10,
|
||||||
|
expHeadHeader: 10,
|
||||||
|
expHeadFastBlock: 10,
|
||||||
|
expHeadBlock: 10,
|
||||||
|
expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests a Geth was running with leagcy snapshot enabled. Then restarts
|
||||||
|
// without enabling snapshot and after that re-enable the snapshot again.
|
||||||
|
// In this case the snapshot should be rebuilt with latest chain head.
|
||||||
|
func TestGappedLegacySnapshot(t *testing.T) {
|
||||||
|
// Chain:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
|
//
|
||||||
|
// Commit: G
|
||||||
|
// Snapshot: G
|
||||||
|
//
|
||||||
|
// SetHead(0)
|
||||||
|
//
|
||||||
|
// ------------------------------
|
||||||
|
//
|
||||||
|
// Expected in leveldb:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10
|
||||||
|
//
|
||||||
|
// Expected head header : C10
|
||||||
|
// Expected head fast block: C10
|
||||||
|
// Expected head block : C10
|
||||||
|
// Expected snapshot disk : C10
|
||||||
|
testSnapshot(t, &snapshotTest{
|
||||||
|
legacy: true,
|
||||||
|
crash: false,
|
||||||
|
gapped: 2,
|
||||||
|
setHead: 0,
|
||||||
|
chainBlocks: 8,
|
||||||
|
snapshotBlock: 0,
|
||||||
|
commitBlock: 0,
|
||||||
|
expCanonicalBlocks: 10,
|
||||||
|
expHeadHeader: 10,
|
||||||
|
expHeadFastBlock: 10,
|
||||||
|
expHeadBlock: 10,
|
||||||
|
expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests the Geth was running with snapshot enabled and resetHead is applied.
|
||||||
|
// In this case the head is rewound to the target(with state available). After
|
||||||
|
// that the chain is restarted and the original disk layer is kept.
|
||||||
|
func TestSetHeadWithNewSnapshot(t *testing.T) {
|
||||||
|
// Chain:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
|
//
|
||||||
|
// Commit: G
|
||||||
|
// Snapshot: G
|
||||||
|
//
|
||||||
|
// SetHead(4)
|
||||||
|
//
|
||||||
|
// ------------------------------
|
||||||
|
//
|
||||||
|
// Expected in leveldb:
|
||||||
|
// G->C1->C2->C3->C4
|
||||||
|
//
|
||||||
|
// Expected head header : C4
|
||||||
|
// Expected head fast block: C4
|
||||||
|
// Expected head block : C4
|
||||||
|
// Expected snapshot disk : G
|
||||||
|
testSnapshot(t, &snapshotTest{
|
||||||
|
legacy: false,
|
||||||
|
crash: false,
|
||||||
|
gapped: 0,
|
||||||
|
setHead: 4,
|
||||||
|
chainBlocks: 8,
|
||||||
|
snapshotBlock: 0,
|
||||||
|
commitBlock: 0,
|
||||||
|
expCanonicalBlocks: 4,
|
||||||
|
expHeadHeader: 4,
|
||||||
|
expHeadFastBlock: 4,
|
||||||
|
expHeadBlock: 4,
|
||||||
|
expSnapshotBottom: 0, // The initial disk layer is built from the genesis
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests the Geth was running with snapshot(legacy-format) enabled and resetHead
|
||||||
|
// is applied. In this case the head is rewound to the target(with state available).
|
||||||
|
// After that the chain is restarted and the original disk layer is kept.
|
||||||
|
func TestSetHeadWithLegacySnapshot(t *testing.T) {
|
||||||
|
// Chain:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
|
//
|
||||||
|
// Commit: G
|
||||||
|
// Snapshot: G
|
||||||
|
//
|
||||||
|
// SetHead(4)
|
||||||
|
//
|
||||||
|
// ------------------------------
|
||||||
|
//
|
||||||
|
// Expected in leveldb:
|
||||||
|
// G->C1->C2->C3->C4
|
||||||
|
//
|
||||||
|
// Expected head header : C4
|
||||||
|
// Expected head fast block: C4
|
||||||
|
// Expected head block : C4
|
||||||
|
// Expected snapshot disk : G
|
||||||
|
testSnapshot(t, &snapshotTest{
|
||||||
|
legacy: true,
|
||||||
|
crash: false,
|
||||||
|
gapped: 0,
|
||||||
|
setHead: 4,
|
||||||
|
chainBlocks: 8,
|
||||||
|
snapshotBlock: 0,
|
||||||
|
commitBlock: 0,
|
||||||
|
expCanonicalBlocks: 4,
|
||||||
|
expHeadHeader: 4,
|
||||||
|
expHeadFastBlock: 4,
|
||||||
|
expHeadBlock: 4,
|
||||||
|
expSnapshotBottom: 0, // The initial disk layer is built from the genesis
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests the Geth was running with snapshot(legacy-format) enabled and upgrades
|
||||||
|
// the disk layer journal(journal generator) to latest format. After that the Geth
|
||||||
|
// is restarted from a crash. In this case Geth will find the new-format disk layer
|
||||||
|
// journal but with legacy-format diff journal(the new-format is never committed),
|
||||||
|
// and the invalid diff journal is expected to be dropped.
|
||||||
|
func TestRecoverSnapshotFromCrashWithLegacyDiffJournal(t *testing.T) {
|
||||||
|
// Chain:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||||
|
//
|
||||||
|
// Commit: G
|
||||||
|
// Snapshot: G
|
||||||
|
//
|
||||||
|
// SetHead(0)
|
||||||
|
//
|
||||||
|
// ------------------------------
|
||||||
|
//
|
||||||
|
// Expected in leveldb:
|
||||||
|
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10
|
||||||
|
//
|
||||||
|
// Expected head header : C10
|
||||||
|
// Expected head fast block: C10
|
||||||
|
// Expected head block : C8
|
||||||
|
// Expected snapshot disk : C10
|
||||||
|
testSnapshot(t, &snapshotTest{
|
||||||
|
legacy: true,
|
||||||
|
crash: false,
|
||||||
|
restartCrash: 2,
|
||||||
|
gapped: 0,
|
||||||
|
setHead: 0,
|
||||||
|
chainBlocks: 8,
|
||||||
|
snapshotBlock: 0,
|
||||||
|
commitBlock: 0,
|
||||||
|
expCanonicalBlocks: 10,
|
||||||
|
expHeadHeader: 10,
|
||||||
|
expHeadFastBlock: 10,
|
||||||
|
expHeadBlock: 8, // The persisted state in the first running
|
||||||
|
expSnapshotBottom: 10, // The persisted disk layer in the second running
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSnapshot(t *testing.T, tt *snapshotTest) {
|
||||||
|
// It's hard to follow the test case, visualize the input
|
||||||
|
// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
||||||
|
// fmt.Println(tt.dump())
|
||||||
|
|
||||||
|
// Create a temporary persistent database
|
||||||
|
datadir, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temporary datadir: %v", err)
|
||||||
|
}
|
||||||
|
os.RemoveAll(datadir)
|
||||||
|
|
||||||
|
db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create persistent database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close() // Might double close, should be fine
|
||||||
|
|
||||||
|
// Initialize a fresh chain
|
||||||
|
var (
|
||||||
|
genesis = new(Genesis).MustCommit(db)
|
||||||
|
engine = ethash.NewFullFaker()
|
||||||
|
gendb = rawdb.NewMemoryDatabase()
|
||||||
|
|
||||||
|
// Snapshot is enabled, the first snapshot is created from the Genesis.
|
||||||
|
// The snapshot memory allowance is 256MB, it means no snapshot flush
|
||||||
|
// will happen during the block insertion.
|
||||||
|
cacheConfig = defaultCacheConfig
|
||||||
|
)
|
||||||
|
chain, err := NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create chain: %v", err)
|
||||||
|
}
|
||||||
|
blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, gendb, tt.chainBlocks, func(i int, b *BlockGen) {})
|
||||||
|
|
||||||
|
// Insert the blocks with configured settings.
|
||||||
|
var breakpoints []uint64
|
||||||
|
if tt.commitBlock > tt.snapshotBlock {
|
||||||
|
breakpoints = append(breakpoints, tt.snapshotBlock, tt.commitBlock)
|
||||||
|
} else {
|
||||||
|
breakpoints = append(breakpoints, tt.commitBlock, tt.snapshotBlock)
|
||||||
|
}
|
||||||
|
var startPoint uint64
|
||||||
|
for _, point := range breakpoints {
|
||||||
|
if _, err := chain.InsertChain(blocks[startPoint:point]); err != nil {
|
||||||
|
t.Fatalf("Failed to import canonical chain start: %v", err)
|
||||||
|
}
|
||||||
|
startPoint = point
|
||||||
|
|
||||||
|
if tt.commitBlock > 0 && tt.commitBlock == point {
|
||||||
|
chain.stateCache.TrieDB().Commit(blocks[point-1].Root(), true, nil)
|
||||||
|
}
|
||||||
|
if tt.snapshotBlock > 0 && tt.snapshotBlock == point {
|
||||||
|
if tt.legacy {
|
||||||
|
// Here we commit the snapshot disk root to simulate
|
||||||
|
// committing the legacy snapshot.
|
||||||
|
rawdb.WriteSnapshotRoot(db, blocks[point-1].Root())
|
||||||
|
} else {
|
||||||
|
chain.snaps.Cap(blocks[point-1].Root(), 0)
|
||||||
|
diskRoot, blockRoot := chain.snaps.DiskRoot(), blocks[point-1].Root()
|
||||||
|
if !bytes.Equal(diskRoot.Bytes(), blockRoot.Bytes()) {
|
||||||
|
t.Fatalf("Failed to flush disk layer change, want %x, got %x", blockRoot, diskRoot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := chain.InsertChain(blocks[startPoint:]); err != nil {
|
||||||
|
t.Fatalf("Failed to import canonical chain tail: %v", err)
|
||||||
|
}
|
||||||
|
// Set the flag for writing legacy journal if necessary
|
||||||
|
if tt.legacy {
|
||||||
|
chain.writeLegacyJournal = true
|
||||||
|
}
|
||||||
|
// Pull the plug on the database, simulating a hard crash
|
||||||
|
if tt.crash {
|
||||||
|
db.Close()
|
||||||
|
|
||||||
|
// Start a new blockchain back up and see where the repair leads us
|
||||||
|
db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to reopen persistent database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// The interesting thing is: instead of start the blockchain after
|
||||||
|
// the crash, we do restart twice here: one after the crash and one
|
||||||
|
// after the normal stop. It's used to ensure the broken snapshot
|
||||||
|
// can be detected all the time.
|
||||||
|
chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to recreate chain: %v", err)
|
||||||
|
}
|
||||||
|
chain.Stop()
|
||||||
|
|
||||||
|
chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to recreate chain: %v", err)
|
||||||
|
}
|
||||||
|
defer chain.Stop()
|
||||||
|
} else if tt.gapped > 0 {
|
||||||
|
// Insert blocks without enabling snapshot if gapping is required.
|
||||||
|
chain.Stop()
|
||||||
|
gappedBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], engine, gendb, tt.gapped, func(i int, b *BlockGen) {})
|
||||||
|
|
||||||
|
// Insert a few more blocks without enabling snapshot
|
||||||
|
var cacheConfig = &CacheConfig{
|
||||||
|
TrieCleanLimit: 256,
|
||||||
|
TrieDirtyLimit: 256,
|
||||||
|
TrieTimeLimit: 5 * time.Minute,
|
||||||
|
SnapshotLimit: 0,
|
||||||
|
}
|
||||||
|
chain, err = NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to recreate chain: %v", err)
|
||||||
|
}
|
||||||
|
chain.InsertChain(gappedBlocks)
|
||||||
|
chain.Stop()
|
||||||
|
|
||||||
|
chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to recreate chain: %v", err)
|
||||||
|
}
|
||||||
|
defer chain.Stop()
|
||||||
|
} else if tt.setHead != 0 {
|
||||||
|
// Rewind the chain if setHead operation is required.
|
||||||
|
chain.SetHead(tt.setHead)
|
||||||
|
chain.Stop()
|
||||||
|
|
||||||
|
chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to recreate chain: %v", err)
|
||||||
|
}
|
||||||
|
defer chain.Stop()
|
||||||
|
} else if tt.restartCrash != 0 {
|
||||||
|
// Firstly, stop the chain properly, with all snapshot journal
|
||||||
|
// and state committed.
|
||||||
|
chain.Stop()
|
||||||
|
|
||||||
|
// Restart chain, forcibly flush the disk layer journal with new format
|
||||||
|
newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], engine, gendb, tt.restartCrash, func(i int, b *BlockGen) {})
|
||||||
|
chain, err = NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to recreate chain: %v", err)
|
||||||
|
}
|
||||||
|
chain.InsertChain(newBlocks)
|
||||||
|
chain.Snapshot().Cap(newBlocks[len(newBlocks)-1].Root(), 0)
|
||||||
|
|
||||||
|
// Simulate the blockchain crash
|
||||||
|
// Don't call chain.Stop here, so that no snapshot
|
||||||
|
// journal and latest state will be committed
|
||||||
|
|
||||||
|
// Restart the chain after the crash
|
||||||
|
chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to recreate chain: %v", err)
|
||||||
|
}
|
||||||
|
defer chain.Stop()
|
||||||
|
} else {
|
||||||
|
chain.Stop()
|
||||||
|
|
||||||
|
// Restart the chain normally
|
||||||
|
chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to recreate chain: %v", err)
|
||||||
|
}
|
||||||
|
defer chain.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over all the remaining blocks and ensure there are no gaps
|
||||||
|
verifyNoGaps(t, chain, true, blocks)
|
||||||
|
verifyCutoff(t, chain, true, blocks, tt.expCanonicalBlocks)
|
||||||
|
|
||||||
|
if head := chain.CurrentHeader(); head.Number.Uint64() != tt.expHeadHeader {
|
||||||
|
t.Errorf("Head header mismatch: have %d, want %d", head.Number, tt.expHeadHeader)
|
||||||
|
}
|
||||||
|
if head := chain.CurrentFastBlock(); head.NumberU64() != tt.expHeadFastBlock {
|
||||||
|
t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), tt.expHeadFastBlock)
|
||||||
|
}
|
||||||
|
if head := chain.CurrentBlock(); head.NumberU64() != tt.expHeadBlock {
|
||||||
|
t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), tt.expHeadBlock)
|
||||||
|
}
|
||||||
|
// Check the disk layer, ensure they are matched
|
||||||
|
block := chain.GetBlockByNumber(tt.expSnapshotBottom)
|
||||||
|
if block == nil {
|
||||||
|
t.Errorf("The correspnding block[%d] of snapshot disk layer is missing", tt.expSnapshotBottom)
|
||||||
|
} else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) {
|
||||||
|
t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -243,8 +243,8 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
|
||||||
return params.RinkebyChainConfig
|
return params.RinkebyChainConfig
|
||||||
case ghash == params.GoerliGenesisHash:
|
case ghash == params.GoerliGenesisHash:
|
||||||
return params.GoerliChainConfig
|
return params.GoerliChainConfig
|
||||||
case ghash == params.YoloV1GenesisHash:
|
case ghash == params.YoloV2GenesisHash:
|
||||||
return params.YoloV1ChainConfig
|
return params.YoloV2ChainConfig
|
||||||
default:
|
default:
|
||||||
return params.AllEthashProtocolChanges
|
return params.AllEthashProtocolChanges
|
||||||
}
|
}
|
||||||
|
|
@ -380,10 +380,11 @@ func DefaultGoerliGenesisBlock() *Genesis {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultYoloV1GenesisBlock() *Genesis {
|
func DefaultYoloV2GenesisBlock() *Genesis {
|
||||||
|
// TODO: Update with yolov2 values + regenerate alloc data
|
||||||
return &Genesis{
|
return &Genesis{
|
||||||
Config: params.YoloV1ChainConfig,
|
Config: params.YoloV2ChainConfig,
|
||||||
Timestamp: 0x5ed754f1,
|
Timestamp: 0x5f91b932,
|
||||||
ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000008a37866fd3627c9205a37c8685666f32ec07bb1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000008a37866fd3627c9205a37c8685666f32ec07bb1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
||||||
GasLimit: 0x47b760,
|
GasLimit: 0x47b760,
|
||||||
Difficulty: big.NewInt(1),
|
Difficulty: big.NewInt(1),
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
package rawdb
|
package rawdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -118,3 +120,58 @@ func DeleteSnapshotJournal(db ethdb.KeyValueWriter) {
|
||||||
log.Crit("Failed to remove snapshot journal", "err", err)
|
log.Crit("Failed to remove snapshot journal", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadSnapshotGenerator retrieves the serialized snapshot generator saved at
|
||||||
|
// the last shutdown.
|
||||||
|
func ReadSnapshotGenerator(db ethdb.KeyValueReader) []byte {
|
||||||
|
data, _ := db.Get(snapshotGeneratorKey)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteSnapshotGenerator stores the serialized snapshot generator to save at
|
||||||
|
// shutdown.
|
||||||
|
func WriteSnapshotGenerator(db ethdb.KeyValueWriter, generator []byte) {
|
||||||
|
if err := db.Put(snapshotGeneratorKey, generator); err != nil {
|
||||||
|
log.Crit("Failed to store snapshot generator", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSnapshotGenerator deletes the serialized snapshot generator saved at
|
||||||
|
// the last shutdown
|
||||||
|
func DeleteSnapshotGenerator(db ethdb.KeyValueWriter) {
|
||||||
|
if err := db.Delete(snapshotGeneratorKey); err != nil {
|
||||||
|
log.Crit("Failed to remove snapshot generator", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSnapshotRecoveryNumber retrieves the block number of the last persisted
|
||||||
|
// snapshot layer.
|
||||||
|
func ReadSnapshotRecoveryNumber(db ethdb.KeyValueReader) *uint64 {
|
||||||
|
data, _ := db.Get(snapshotRecoveryKey)
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(data) != 8 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
number := binary.BigEndian.Uint64(data)
|
||||||
|
return &number
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteSnapshotRecoveryNumber stores the block number of the last persisted
|
||||||
|
// snapshot layer.
|
||||||
|
func WriteSnapshotRecoveryNumber(db ethdb.KeyValueWriter, number uint64) {
|
||||||
|
var buf [8]byte
|
||||||
|
binary.BigEndian.PutUint64(buf[:], number)
|
||||||
|
if err := db.Put(snapshotRecoveryKey, buf[:]); err != nil {
|
||||||
|
log.Crit("Failed to store snapshot recovery number", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSnapshotRecoveryNumber deletes the block number of the last persisted
|
||||||
|
// snapshot layer.
|
||||||
|
func DeleteSnapshotRecoveryNumber(db ethdb.KeyValueWriter) {
|
||||||
|
if err := db.Delete(snapshotRecoveryKey); err != nil {
|
||||||
|
log.Crit("Failed to remove snapshot recovery number", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,15 +84,17 @@ type blockTxHashes struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterateTransactions iterates over all transactions in the (canon) block
|
// iterateTransactions iterates over all transactions in the (canon) block
|
||||||
// number(s) given, and yields the hashes on a channel
|
// number(s) given, and yields the hashes on a channel. If there is a signal
|
||||||
func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool) (chan *blockTxHashes, chan struct{}) {
|
// received from interrupt channel, the iteration will be aborted and result
|
||||||
|
// channel will be closed.
|
||||||
|
func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool, interrupt chan struct{}) chan *blockTxHashes {
|
||||||
// One thread sequentially reads data from db
|
// One thread sequentially reads data from db
|
||||||
type numberRlp struct {
|
type numberRlp struct {
|
||||||
number uint64
|
number uint64
|
||||||
rlp rlp.RawValue
|
rlp rlp.RawValue
|
||||||
}
|
}
|
||||||
if to == from {
|
if to == from {
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
threads := to - from
|
threads := to - from
|
||||||
if cpus := runtime.NumCPU(); threads > uint64(cpus) {
|
if cpus := runtime.NumCPU(); threads > uint64(cpus) {
|
||||||
|
|
@ -101,7 +103,6 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool
|
||||||
var (
|
var (
|
||||||
rlpCh = make(chan *numberRlp, threads*2) // we send raw rlp over this channel
|
rlpCh = make(chan *numberRlp, threads*2) // we send raw rlp over this channel
|
||||||
hashesCh = make(chan *blockTxHashes, threads*2) // send hashes over hashesCh
|
hashesCh = make(chan *blockTxHashes, threads*2) // send hashes over hashesCh
|
||||||
abortCh = make(chan struct{})
|
|
||||||
)
|
)
|
||||||
// lookup runs in one instance
|
// lookup runs in one instance
|
||||||
lookup := func() {
|
lookup := func() {
|
||||||
|
|
@ -115,7 +116,7 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool
|
||||||
// Feed the block to the aggregator, or abort on interrupt
|
// Feed the block to the aggregator, or abort on interrupt
|
||||||
select {
|
select {
|
||||||
case rlpCh <- &numberRlp{n, data}:
|
case rlpCh <- &numberRlp{n, data}:
|
||||||
case <-abortCh:
|
case <-interrupt:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if reverse {
|
if reverse {
|
||||||
|
|
@ -168,7 +169,7 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool
|
||||||
// Feed the block to the aggregator, or abort on interrupt
|
// Feed the block to the aggregator, or abort on interrupt
|
||||||
select {
|
select {
|
||||||
case hashesCh <- result:
|
case hashesCh <- result:
|
||||||
case <-abortCh:
|
case <-interrupt:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -177,25 +178,28 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool
|
||||||
for i := 0; i < int(threads); i++ {
|
for i := 0; i < int(threads); i++ {
|
||||||
go process()
|
go process()
|
||||||
}
|
}
|
||||||
return hashesCh, abortCh
|
return hashesCh
|
||||||
}
|
}
|
||||||
|
|
||||||
// IndexTransactions creates txlookup indices of the specified block range.
|
// indexTransactions creates txlookup indices of the specified block range.
|
||||||
//
|
//
|
||||||
// This function iterates canonical chain in reverse order, it has one main advantage:
|
// This function iterates canonical chain in reverse order, it has one main advantage:
|
||||||
// We can write tx index tail flag periodically even without the whole indexing
|
// We can write tx index tail flag periodically even without the whole indexing
|
||||||
// procedure is finished. So that we can resume indexing procedure next time quickly.
|
// procedure is finished. So that we can resume indexing procedure next time quickly.
|
||||||
func IndexTransactions(db ethdb.Database, from uint64, to uint64) {
|
//
|
||||||
|
// There is a passed channel, the whole procedure will be interrupted if any
|
||||||
|
// signal received.
|
||||||
|
func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) {
|
||||||
// short circuit for invalid range
|
// short circuit for invalid range
|
||||||
if from >= to {
|
if from >= to {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
hashesCh, abortCh = iterateTransactions(db, from, to, true)
|
hashesCh = iterateTransactions(db, from, to, true, interrupt)
|
||||||
batch = db.NewBatch()
|
batch = db.NewBatch()
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
logged = start.Add(-7 * time.Second)
|
logged = start.Add(-7 * time.Second)
|
||||||
// Since we iterate in reverse, we expect the first number to come
|
// Since we iterate in reverse, we expect the first number to come
|
||||||
// in to be [to-1]. Therefore, setting lastNum to means that the
|
// in to be [to-1]. Therefore, setting lastNum to means that the
|
||||||
// prqueue gap-evaluation will work correctly
|
// prqueue gap-evaluation will work correctly
|
||||||
lastNum = to
|
lastNum = to
|
||||||
|
|
@ -203,8 +207,6 @@ func IndexTransactions(db ethdb.Database, from uint64, to uint64) {
|
||||||
// for stats reporting
|
// for stats reporting
|
||||||
blocks, txs = 0, 0
|
blocks, txs = 0, 0
|
||||||
)
|
)
|
||||||
defer close(abortCh)
|
|
||||||
|
|
||||||
for chanDelivery := range hashesCh {
|
for chanDelivery := range hashesCh {
|
||||||
// Push the delivery into the queue and process contiguous ranges.
|
// Push the delivery into the queue and process contiguous ranges.
|
||||||
// Since we iterate in reverse, so lower numbers have lower prio, and
|
// Since we iterate in reverse, so lower numbers have lower prio, and
|
||||||
|
|
@ -215,6 +217,10 @@ func IndexTransactions(db ethdb.Database, from uint64, to uint64) {
|
||||||
if _, priority := queue.Peek(); priority != int64(lastNum-1) {
|
if _, priority := queue.Peek(); priority != int64(lastNum-1) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
// For testing
|
||||||
|
if hook != nil && !hook(lastNum-1) {
|
||||||
|
break
|
||||||
|
}
|
||||||
// Next block available, pop it off and index it
|
// Next block available, pop it off and index it
|
||||||
delivery := queue.PopItem().(*blockTxHashes)
|
delivery := queue.PopItem().(*blockTxHashes)
|
||||||
lastNum = delivery.number
|
lastNum = delivery.number
|
||||||
|
|
@ -223,8 +229,7 @@ func IndexTransactions(db ethdb.Database, from uint64, to uint64) {
|
||||||
txs += len(delivery.hashes)
|
txs += len(delivery.hashes)
|
||||||
// If enough data was accumulated in memory or we're at the last block, dump to disk
|
// If enough data was accumulated in memory or we're at the last block, dump to disk
|
||||||
if batch.ValueSize() > ethdb.IdealBatchSize {
|
if batch.ValueSize() > ethdb.IdealBatchSize {
|
||||||
// Also write the tail there
|
WriteTxIndexTail(batch, lastNum) // Also write the tail here
|
||||||
WriteTxIndexTail(batch, lastNum)
|
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
log.Crit("Failed writing batch to db", "error", err)
|
log.Crit("Failed writing batch to db", "error", err)
|
||||||
return
|
return
|
||||||
|
|
@ -238,67 +243,122 @@ func IndexTransactions(db ethdb.Database, from uint64, to uint64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if lastNum < to {
|
// If there exists uncommitted data, flush them.
|
||||||
WriteTxIndexTail(batch, lastNum)
|
if batch.ValueSize() > 0 {
|
||||||
// No need to write the batch if we never entered the loop above...
|
WriteTxIndexTail(batch, lastNum) // Also write the tail there
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
log.Crit("Failed writing batch to db", "error", err)
|
log.Crit("Failed writing batch to db", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Info("Indexed transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start)))
|
select {
|
||||||
|
case <-interrupt:
|
||||||
|
log.Debug("Transaction indexing interrupted", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
default:
|
||||||
|
log.Info("Indexed transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnindexTransactions removes txlookup indices of the specified block range.
|
// IndexTransactions creates txlookup indices of the specified block range.
|
||||||
func UnindexTransactions(db ethdb.Database, from uint64, to uint64) {
|
//
|
||||||
|
// This function iterates canonical chain in reverse order, it has one main advantage:
|
||||||
|
// We can write tx index tail flag periodically even without the whole indexing
|
||||||
|
// procedure is finished. So that we can resume indexing procedure next time quickly.
|
||||||
|
//
|
||||||
|
// There is a passed channel, the whole procedure will be interrupted if any
|
||||||
|
// signal received.
|
||||||
|
func IndexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}) {
|
||||||
|
indexTransactions(db, from, to, interrupt, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// indexTransactionsForTesting is the internal debug version with an additional hook.
|
||||||
|
func indexTransactionsForTesting(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) {
|
||||||
|
indexTransactions(db, from, to, interrupt, hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unindexTransactions removes txlookup indices of the specified block range.
|
||||||
|
//
|
||||||
|
// There is a passed channel, the whole procedure will be interrupted if any
|
||||||
|
// signal received.
|
||||||
|
func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) {
|
||||||
// short circuit for invalid range
|
// short circuit for invalid range
|
||||||
if from >= to {
|
if from >= to {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Write flag first and then unindex the transaction indices. Some indices
|
|
||||||
// will be left in the database if crash happens but it's fine.
|
|
||||||
WriteTxIndexTail(db, to)
|
|
||||||
// If only one block is unindexed, do it directly
|
|
||||||
//if from+1 == to {
|
|
||||||
// data := ReadCanonicalBodyRLP(db, uint64(from))
|
|
||||||
// DeleteTxLookupEntries(db, ReadBlock(db, ReadCanonicalHash(db, from), from))
|
|
||||||
// log.Info("Unindexed transactions", "blocks", 1, "tail", to)
|
|
||||||
// return
|
|
||||||
//}
|
|
||||||
// TODO @holiman, add this back (if we want it)
|
|
||||||
var (
|
var (
|
||||||
hashesCh, abortCh = iterateTransactions(db, from, to, false)
|
hashesCh = iterateTransactions(db, from, to, false, interrupt)
|
||||||
batch = db.NewBatch()
|
batch = db.NewBatch()
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
logged = start.Add(-7 * time.Second)
|
logged = start.Add(-7 * time.Second)
|
||||||
|
// we expect the first number to come in to be [from]. Therefore, setting
|
||||||
|
// nextNum to from means that the prqueue gap-evaluation will work correctly
|
||||||
|
nextNum = from
|
||||||
|
queue = prque.New(nil)
|
||||||
|
// for stats reporting
|
||||||
|
blocks, txs = 0, 0
|
||||||
)
|
)
|
||||||
defer close(abortCh)
|
|
||||||
// Otherwise spin up the concurrent iterator and unindexer
|
// Otherwise spin up the concurrent iterator and unindexer
|
||||||
blocks, txs := 0, 0
|
|
||||||
for delivery := range hashesCh {
|
for delivery := range hashesCh {
|
||||||
DeleteTxLookupEntries(batch, delivery.hashes)
|
// Push the delivery into the queue and process contiguous ranges.
|
||||||
txs += len(delivery.hashes)
|
queue.Push(delivery, -int64(delivery.number))
|
||||||
blocks++
|
for !queue.Empty() {
|
||||||
|
// If the next available item is gapped, return
|
||||||
// If enough data was accumulated in memory or we're at the last block, dump to disk
|
if _, priority := queue.Peek(); -priority != int64(nextNum) {
|
||||||
// A batch counts the size of deletion as '1', so we need to flush more
|
break
|
||||||
// often than that.
|
}
|
||||||
if blocks%1000 == 0 {
|
// For testing
|
||||||
if err := batch.Write(); err != nil {
|
if hook != nil && !hook(nextNum) {
|
||||||
log.Crit("Failed writing batch to db", "error", err)
|
break
|
||||||
return
|
}
|
||||||
|
delivery := queue.PopItem().(*blockTxHashes)
|
||||||
|
nextNum = delivery.number + 1
|
||||||
|
DeleteTxLookupEntries(batch, delivery.hashes)
|
||||||
|
txs += len(delivery.hashes)
|
||||||
|
blocks++
|
||||||
|
|
||||||
|
// If enough data was accumulated in memory or we're at the last block, dump to disk
|
||||||
|
// A batch counts the size of deletion as '1', so we need to flush more
|
||||||
|
// often than that.
|
||||||
|
if blocks%1000 == 0 {
|
||||||
|
WriteTxIndexTail(batch, nextNum)
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Crit("Failed writing batch to db", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
batch.Reset()
|
||||||
|
}
|
||||||
|
// If we've spent too much time already, notify the user of what we're doing
|
||||||
|
if time.Since(logged) > 8*time.Second {
|
||||||
|
log.Info("Unindexing transactions", "blocks", blocks, "txs", txs, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
logged = time.Now()
|
||||||
}
|
}
|
||||||
batch.Reset()
|
|
||||||
}
|
|
||||||
// If we've spent too much time already, notify the user of what we're doing
|
|
||||||
if time.Since(logged) > 8*time.Second {
|
|
||||||
log.Info("Unindexing transactions", "blocks", blocks, "txs", txs, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
logged = time.Now()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := batch.Write(); err != nil {
|
// Commit the last batch if there exists uncommitted data
|
||||||
log.Crit("Failed writing batch to db", "error", err)
|
if batch.ValueSize() > 0 {
|
||||||
return
|
WriteTxIndexTail(batch, nextNum)
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Crit("Failed writing batch to db", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-interrupt:
|
||||||
|
log.Debug("Transaction unindexing interrupted", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
default:
|
||||||
|
log.Info("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
}
|
}
|
||||||
log.Info("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start)))
|
}
|
||||||
|
|
||||||
|
// UnindexTransactions removes txlookup indices of the specified block range.
|
||||||
|
//
|
||||||
|
// There is a passed channel, the whole procedure will be interrupted if any
|
||||||
|
// signal received.
|
||||||
|
func UnindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}) {
|
||||||
|
unindexTransactions(db, from, to, interrupt, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unindexTransactionsForTesting is the internal debug version with an additional hook.
|
||||||
|
func unindexTransactionsForTesting(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) {
|
||||||
|
unindexTransactions(db, from, to, interrupt, hook)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -59,7 +60,7 @@ func TestChainIterator(t *testing.T) {
|
||||||
}
|
}
|
||||||
for i, c := range cases {
|
for i, c := range cases {
|
||||||
var numbers []int
|
var numbers []int
|
||||||
hashCh, _ := iterateTransactions(chainDb, c.from, c.to, c.reverse)
|
hashCh := iterateTransactions(chainDb, c.from, c.to, c.reverse, nil)
|
||||||
if hashCh != nil {
|
if hashCh != nil {
|
||||||
for h := range hashCh {
|
for h := range hashCh {
|
||||||
numbers = append(numbers, int(h.number))
|
numbers = append(numbers, int(h.number))
|
||||||
|
|
@ -80,3 +81,85 @@ func TestChainIterator(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIndexTransactions(t *testing.T) {
|
||||||
|
// Construct test chain db
|
||||||
|
chainDb := NewMemoryDatabase()
|
||||||
|
|
||||||
|
var block *types.Block
|
||||||
|
var txs []*types.Transaction
|
||||||
|
for i := uint64(0); i <= 10; i++ {
|
||||||
|
if i == 0 {
|
||||||
|
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, nil, nil, nil, newHasher()) // Empty genesis block
|
||||||
|
} else {
|
||||||
|
tx := types.NewTransaction(i, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11})
|
||||||
|
txs = append(txs, tx)
|
||||||
|
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher())
|
||||||
|
}
|
||||||
|
WriteBlock(chainDb, block)
|
||||||
|
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
|
||||||
|
}
|
||||||
|
// verify checks whether the tx indices in the range [from, to)
|
||||||
|
// is expected.
|
||||||
|
verify := func(from, to int, exist bool, tail uint64) {
|
||||||
|
for i := from; i < to; i++ {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
number := ReadTxLookupEntry(chainDb, txs[i-1].Hash())
|
||||||
|
if exist && number == nil {
|
||||||
|
t.Fatalf("Transaction indice missing")
|
||||||
|
}
|
||||||
|
if !exist && number != nil {
|
||||||
|
t.Fatalf("Transaction indice is not deleted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
number := ReadTxIndexTail(chainDb)
|
||||||
|
if number == nil || *number != tail {
|
||||||
|
t.Fatalf("Transaction tail mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IndexTransactions(chainDb, 5, 11, nil)
|
||||||
|
verify(5, 11, true, 5)
|
||||||
|
verify(0, 5, false, 5)
|
||||||
|
|
||||||
|
IndexTransactions(chainDb, 0, 5, nil)
|
||||||
|
verify(0, 11, true, 0)
|
||||||
|
|
||||||
|
UnindexTransactions(chainDb, 0, 5, nil)
|
||||||
|
verify(5, 11, true, 5)
|
||||||
|
verify(0, 5, false, 5)
|
||||||
|
|
||||||
|
UnindexTransactions(chainDb, 5, 11, nil)
|
||||||
|
verify(0, 11, false, 11)
|
||||||
|
|
||||||
|
// Testing corner cases
|
||||||
|
signal := make(chan struct{})
|
||||||
|
var once sync.Once
|
||||||
|
indexTransactionsForTesting(chainDb, 5, 11, signal, func(n uint64) bool {
|
||||||
|
if n <= 8 {
|
||||||
|
once.Do(func() {
|
||||||
|
close(signal)
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
verify(9, 11, true, 9)
|
||||||
|
verify(0, 9, false, 9)
|
||||||
|
IndexTransactions(chainDb, 0, 9, nil)
|
||||||
|
|
||||||
|
signal = make(chan struct{})
|
||||||
|
var once2 sync.Once
|
||||||
|
unindexTransactionsForTesting(chainDb, 0, 11, signal, func(n uint64) bool {
|
||||||
|
if n >= 8 {
|
||||||
|
once2.Do(func() {
|
||||||
|
close(signal)
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
verify(8, 11, true, 8)
|
||||||
|
verify(0, 8, false, 8)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,12 @@ var (
|
||||||
// snapshotJournalKey tracks the in-memory diff layers across restarts.
|
// snapshotJournalKey tracks the in-memory diff layers across restarts.
|
||||||
snapshotJournalKey = []byte("SnapshotJournal")
|
snapshotJournalKey = []byte("SnapshotJournal")
|
||||||
|
|
||||||
|
// snapshotGeneratorKey tracks the snapshot generation marker across restarts.
|
||||||
|
snapshotGeneratorKey = []byte("SnapshotGenerator")
|
||||||
|
|
||||||
|
// snapshotRecoveryKey tracks the snapshot recovery marker across restarts.
|
||||||
|
snapshotRecoveryKey = []byte("SnapshotRecovery")
|
||||||
|
|
||||||
// txIndexTailKey tracks the oldest block whose transactions have been indexed.
|
// txIndexTailKey tracks the oldest block whose transactions have been indexed.
|
||||||
txIndexTailKey = []byte("TransactionIndexTail")
|
txIndexTailKey = []byte("TransactionIndexTail")
|
||||||
|
|
||||||
|
|
|
||||||
136
core/state/access_list.go
Normal file
136
core/state/access_list.go
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
// Copyright 2020 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 (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type accessList struct {
|
||||||
|
addresses map[common.Address]int
|
||||||
|
slots []map[common.Hash]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsAddress returns true if the address is in the access list.
|
||||||
|
func (al *accessList) ContainsAddress(address common.Address) bool {
|
||||||
|
_, ok := al.addresses[address]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains checks if a slot within an account is present in the access list, returning
|
||||||
|
// separate flags for the presence of the account and the slot respectively.
|
||||||
|
func (al *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
|
||||||
|
idx, ok := al.addresses[address]
|
||||||
|
if !ok {
|
||||||
|
// no such address (and hence zero slots)
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
if idx == -1 {
|
||||||
|
// address yes, but no slots
|
||||||
|
return true, false
|
||||||
|
}
|
||||||
|
_, slotPresent = al.slots[idx][slot]
|
||||||
|
return true, slotPresent
|
||||||
|
}
|
||||||
|
|
||||||
|
// newAccessList creates a new accessList.
|
||||||
|
func newAccessList() *accessList {
|
||||||
|
return &accessList{
|
||||||
|
addresses: make(map[common.Address]int),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy creates an independent copy of an accessList.
|
||||||
|
func (a *accessList) Copy() *accessList {
|
||||||
|
cp := newAccessList()
|
||||||
|
for k, v := range a.addresses {
|
||||||
|
cp.addresses[k] = v
|
||||||
|
}
|
||||||
|
cp.slots = make([]map[common.Hash]struct{}, len(a.slots))
|
||||||
|
for i, slotMap := range a.slots {
|
||||||
|
newSlotmap := make(map[common.Hash]struct{}, len(slotMap))
|
||||||
|
for k := range slotMap {
|
||||||
|
newSlotmap[k] = struct{}{}
|
||||||
|
}
|
||||||
|
cp.slots[i] = newSlotmap
|
||||||
|
}
|
||||||
|
return cp
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAddress adds an address to the access list, and returns 'true' if the operation
|
||||||
|
// caused a change (addr was not previously in the list).
|
||||||
|
func (al *accessList) AddAddress(address common.Address) bool {
|
||||||
|
if _, present := al.addresses[address]; present {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
al.addresses[address] = -1
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSlot adds the specified (addr, slot) combo to the access list.
|
||||||
|
// Return values are:
|
||||||
|
// - address added
|
||||||
|
// - slot added
|
||||||
|
// For any 'true' value returned, a corresponding journal entry must be made.
|
||||||
|
func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) {
|
||||||
|
idx, addrPresent := al.addresses[address]
|
||||||
|
if !addrPresent || idx == -1 {
|
||||||
|
// Address not present, or addr present but no slots there
|
||||||
|
al.addresses[address] = len(al.slots)
|
||||||
|
slotmap := map[common.Hash]struct{}{slot: {}}
|
||||||
|
al.slots = append(al.slots, slotmap)
|
||||||
|
return !addrPresent, true
|
||||||
|
}
|
||||||
|
// There is already an (address,slot) mapping
|
||||||
|
slotmap := al.slots[idx]
|
||||||
|
if _, ok := slotmap[slot]; !ok {
|
||||||
|
slotmap[slot] = struct{}{}
|
||||||
|
// Journal add slot change
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
// No changes required
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSlot removes an (address, slot)-tuple from the access list.
|
||||||
|
// This operation needs to be performed in the same order as the addition happened.
|
||||||
|
// This method is meant to be used by the journal, which maintains ordering of
|
||||||
|
// operations.
|
||||||
|
func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) {
|
||||||
|
idx, addrOk := al.addresses[address]
|
||||||
|
// There are two ways this can fail
|
||||||
|
if !addrOk {
|
||||||
|
panic("reverting slot change, address not present in list")
|
||||||
|
}
|
||||||
|
slotmap := al.slots[idx]
|
||||||
|
delete(slotmap, slot)
|
||||||
|
// If that was the last (first) slot, remove it
|
||||||
|
// Since additions and rollbacks are always performed in order,
|
||||||
|
// we can delete the item without worrying about screwing up later indices
|
||||||
|
if len(slotmap) == 0 {
|
||||||
|
al.slots = al.slots[:idx]
|
||||||
|
al.addresses[address] = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAddress removes an address from the access list. This operation
|
||||||
|
// needs to be performed in the same order as the addition happened.
|
||||||
|
// This method is meant to be used by the journal, which maintains ordering of
|
||||||
|
// operations.
|
||||||
|
func (al *accessList) DeleteAddress(address common.Address) {
|
||||||
|
delete(al.addresses, address)
|
||||||
|
}
|
||||||
|
|
@ -130,6 +130,14 @@ type (
|
||||||
touchChange struct {
|
touchChange struct {
|
||||||
account *common.Address
|
account *common.Address
|
||||||
}
|
}
|
||||||
|
// Changes to the access list
|
||||||
|
accessListAddAccountChange struct {
|
||||||
|
address *common.Address
|
||||||
|
}
|
||||||
|
accessListAddSlotChange struct {
|
||||||
|
address *common.Address
|
||||||
|
slot *common.Hash
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ch createObjectChange) revert(s *StateDB) {
|
func (ch createObjectChange) revert(s *StateDB) {
|
||||||
|
|
@ -234,3 +242,28 @@ func (ch addPreimageChange) revert(s *StateDB) {
|
||||||
func (ch addPreimageChange) dirtied() *common.Address {
|
func (ch addPreimageChange) dirtied() *common.Address {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch accessListAddAccountChange) revert(s *StateDB) {
|
||||||
|
/*
|
||||||
|
One important invariant here, is that whenever a (addr, slot) is added, if the
|
||||||
|
addr is not already present, the add causes two journal entries:
|
||||||
|
- one for the address,
|
||||||
|
- one for the (address,slot)
|
||||||
|
Therefore, when unrolling the change, we can always blindly delete the
|
||||||
|
(addr) at this point, since no storage adds can remain when come upon
|
||||||
|
a single (addr) change.
|
||||||
|
*/
|
||||||
|
s.accessList.DeleteAddress(*ch.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch accessListAddAccountChange) dirtied() *common.Address {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch accessListAddSlotChange) revert(s *StateDB) {
|
||||||
|
s.accessList.DeleteSlot(*ch.address, *ch.slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch accessListAddSlotChange) dirtied() *common.Address {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/ethdb/leveldb"
|
"github.com/ethereum/go-ethereum/ethdb/leveldb"
|
||||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// reverse reverses the contents of a byte slice. It's used to update random accs
|
// reverse reverses the contents of a byte slice. It's used to update random accs
|
||||||
|
|
@ -429,6 +430,81 @@ func TestDiskPartialMerge(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that when the bottom-most diff layer is merged into the disk
|
||||||
|
// layer whether the corresponding generator is persisted correctly.
|
||||||
|
func TestDiskGeneratorPersistence(t *testing.T) {
|
||||||
|
var (
|
||||||
|
accOne = randomHash()
|
||||||
|
accTwo = randomHash()
|
||||||
|
accOneSlotOne = randomHash()
|
||||||
|
accOneSlotTwo = randomHash()
|
||||||
|
|
||||||
|
accThree = randomHash()
|
||||||
|
accThreeSlot = randomHash()
|
||||||
|
baseRoot = randomHash()
|
||||||
|
diffRoot = randomHash()
|
||||||
|
diffTwoRoot = randomHash()
|
||||||
|
genMarker = append(randomHash().Bytes(), randomHash().Bytes()...)
|
||||||
|
)
|
||||||
|
// Testing scenario 1, the disk layer is still under the construction.
|
||||||
|
db := rawdb.NewMemoryDatabase()
|
||||||
|
|
||||||
|
rawdb.WriteAccountSnapshot(db, accOne, accOne[:])
|
||||||
|
rawdb.WriteStorageSnapshot(db, accOne, accOneSlotOne, accOneSlotOne[:])
|
||||||
|
rawdb.WriteStorageSnapshot(db, accOne, accOneSlotTwo, accOneSlotTwo[:])
|
||||||
|
rawdb.WriteSnapshotRoot(db, baseRoot)
|
||||||
|
|
||||||
|
// Create a disk layer based on all above updates
|
||||||
|
snaps := &Tree{
|
||||||
|
layers: map[common.Hash]snapshot{
|
||||||
|
baseRoot: &diskLayer{
|
||||||
|
diskdb: db,
|
||||||
|
cache: fastcache.New(500 * 1024),
|
||||||
|
root: baseRoot,
|
||||||
|
genMarker: genMarker,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// Modify or delete some accounts, flatten everything onto disk
|
||||||
|
if err := snaps.Update(diffRoot, baseRoot, nil, map[common.Hash][]byte{
|
||||||
|
accTwo: accTwo[:],
|
||||||
|
}, nil); err != nil {
|
||||||
|
t.Fatalf("failed to update snapshot tree: %v", err)
|
||||||
|
}
|
||||||
|
if err := snaps.Cap(diffRoot, 0); err != nil {
|
||||||
|
t.Fatalf("failed to flatten snapshot tree: %v", err)
|
||||||
|
}
|
||||||
|
blob := rawdb.ReadSnapshotGenerator(db)
|
||||||
|
var generator journalGenerator
|
||||||
|
if err := rlp.DecodeBytes(blob, &generator); err != nil {
|
||||||
|
t.Fatalf("Failed to decode snapshot generator %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(generator.Marker, genMarker) {
|
||||||
|
t.Fatalf("Generator marker is not matched")
|
||||||
|
}
|
||||||
|
// Test senario 2, the disk layer is fully generated
|
||||||
|
// Modify or delete some accounts, flatten everything onto disk
|
||||||
|
if err := snaps.Update(diffTwoRoot, diffRoot, nil, map[common.Hash][]byte{
|
||||||
|
accThree: accThree.Bytes(),
|
||||||
|
}, map[common.Hash]map[common.Hash][]byte{
|
||||||
|
accThree: {accThreeSlot: accThreeSlot.Bytes()},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("failed to update snapshot tree: %v", err)
|
||||||
|
}
|
||||||
|
diskLayer := snaps.layers[snaps.diskRoot()].(*diskLayer)
|
||||||
|
diskLayer.genMarker = nil // Construction finished
|
||||||
|
if err := snaps.Cap(diffTwoRoot, 0); err != nil {
|
||||||
|
t.Fatalf("failed to flatten snapshot tree: %v", err)
|
||||||
|
}
|
||||||
|
blob = rawdb.ReadSnapshotGenerator(db)
|
||||||
|
if err := rlp.DecodeBytes(blob, &generator); err != nil {
|
||||||
|
t.Fatalf("Failed to decode snapshot generator %v", err)
|
||||||
|
}
|
||||||
|
if len(generator.Marker) != 0 {
|
||||||
|
t.Fatalf("Failed to update snapshot generator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Tests that merging something into a disk layer persists it into the database
|
// Tests that merging something into a disk layer persists it into the database
|
||||||
// and invalidates any previously written and cached values, discarding anything
|
// and invalidates any previously written and cached values, discarding anything
|
||||||
// after the in-progress generation marker.
|
// after the in-progress generation marker.
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package snapshot
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -112,9 +113,42 @@ func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache i
|
||||||
genAbort: make(chan chan *generatorStats),
|
genAbort: make(chan chan *generatorStats),
|
||||||
}
|
}
|
||||||
go base.generate(&generatorStats{wiping: wiper, start: time.Now()})
|
go base.generate(&generatorStats{wiping: wiper, start: time.Now()})
|
||||||
|
log.Debug("Start snapshot generation", "root", root)
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// journalProgress persists the generator stats into the database to resume later.
|
||||||
|
func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorStats) {
|
||||||
|
// Write out the generator marker. Note it's a standalone disk layer generator
|
||||||
|
// which is not mixed with journal. It's ok if the generator is persisted while
|
||||||
|
// journal is not.
|
||||||
|
entry := journalGenerator{
|
||||||
|
Done: marker == nil,
|
||||||
|
Marker: marker,
|
||||||
|
}
|
||||||
|
if stats != nil {
|
||||||
|
entry.Wiping = (stats.wiping != nil)
|
||||||
|
entry.Accounts = stats.accounts
|
||||||
|
entry.Slots = stats.slots
|
||||||
|
entry.Storage = uint64(stats.storage)
|
||||||
|
}
|
||||||
|
blob, err := rlp.EncodeToBytes(entry)
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // Cannot happen, here to catch dev errors
|
||||||
|
}
|
||||||
|
var logstr string
|
||||||
|
switch len(marker) {
|
||||||
|
case 0:
|
||||||
|
logstr = "done"
|
||||||
|
case common.HashLength:
|
||||||
|
logstr = fmt.Sprintf("%#x", marker)
|
||||||
|
default:
|
||||||
|
logstr = fmt.Sprintf("%#x:%#x", marker[:common.HashLength], marker[common.HashLength:])
|
||||||
|
}
|
||||||
|
log.Debug("Journalled generator progress", "progress", logstr)
|
||||||
|
rawdb.WriteSnapshotGenerator(db, blob)
|
||||||
|
}
|
||||||
|
|
||||||
// generate is a background thread that iterates over the state and storage tries,
|
// generate is a background thread that iterates over the state and storage tries,
|
||||||
// constructing the state snapshot. All the arguments are purely for statistics
|
// constructing the state snapshot. All the arguments are purely for statistics
|
||||||
// gethering and logging, since the method surfs the blocks as they arrive, often
|
// gethering and logging, since the method surfs the blocks as they arrive, often
|
||||||
|
|
@ -186,11 +220,15 @@ func (dl *diskLayer) generate(stats *generatorStats) {
|
||||||
if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil {
|
if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil {
|
||||||
// Only write and set the marker if we actually did something useful
|
// Only write and set the marker if we actually did something useful
|
||||||
if batch.ValueSize() > 0 {
|
if batch.ValueSize() > 0 {
|
||||||
|
// Ensure the generator entry is in sync with the data
|
||||||
|
marker := accountHash[:]
|
||||||
|
journalProgress(batch, marker, stats)
|
||||||
|
|
||||||
batch.Write()
|
batch.Write()
|
||||||
batch.Reset()
|
batch.Reset()
|
||||||
|
|
||||||
dl.lock.Lock()
|
dl.lock.Lock()
|
||||||
dl.genMarker = accountHash[:]
|
dl.genMarker = marker
|
||||||
dl.lock.Unlock()
|
dl.lock.Unlock()
|
||||||
}
|
}
|
||||||
if abort != nil {
|
if abort != nil {
|
||||||
|
|
@ -227,11 +265,15 @@ func (dl *diskLayer) generate(stats *generatorStats) {
|
||||||
if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil {
|
if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil {
|
||||||
// Only write and set the marker if we actually did something useful
|
// Only write and set the marker if we actually did something useful
|
||||||
if batch.ValueSize() > 0 {
|
if batch.ValueSize() > 0 {
|
||||||
|
// Ensure the generator entry is in sync with the data
|
||||||
|
marker := append(accountHash[:], storeIt.Key...)
|
||||||
|
journalProgress(batch, marker, stats)
|
||||||
|
|
||||||
batch.Write()
|
batch.Write()
|
||||||
batch.Reset()
|
batch.Reset()
|
||||||
|
|
||||||
dl.lock.Lock()
|
dl.lock.Lock()
|
||||||
dl.genMarker = append(accountHash[:], storeIt.Key...)
|
dl.genMarker = marker
|
||||||
dl.lock.Unlock()
|
dl.lock.Unlock()
|
||||||
}
|
}
|
||||||
if abort != nil {
|
if abort != nil {
|
||||||
|
|
@ -263,6 +305,9 @@ func (dl *diskLayer) generate(stats *generatorStats) {
|
||||||
}
|
}
|
||||||
// Snapshot fully generated, set the marker to nil
|
// Snapshot fully generated, set the marker to nil
|
||||||
if batch.ValueSize() > 0 {
|
if batch.ValueSize() > 0 {
|
||||||
|
// Ensure the generator entry is in sync with the data
|
||||||
|
journalProgress(batch, nil, stats)
|
||||||
|
|
||||||
batch.Write()
|
batch.Write()
|
||||||
}
|
}
|
||||||
log.Info("Generated state snapshot", "accounts", stats.accounts, "slots", stats.slots,
|
log.Info("Generated state snapshot", "accounts", stats.accounts, "slots", stats.slots,
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const journalVersion uint64 = 0
|
||||||
|
|
||||||
// journalGenerator is a disk layer entry containing the generator progress marker.
|
// journalGenerator is a disk layer entry containing the generator progress marker.
|
||||||
type journalGenerator struct {
|
type journalGenerator struct {
|
||||||
Wiping bool // Whether the database was in progress of being wiped
|
Wiping bool // Whether the database was in progress of being wiped
|
||||||
|
|
@ -61,8 +63,91 @@ type journalStorage struct {
|
||||||
Vals [][]byte
|
Vals [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadAndParseLegacyJournal tries to parse the snapshot journal in legacy format.
|
||||||
|
func loadAndParseLegacyJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, journalGenerator, error) {
|
||||||
|
// Retrieve the journal, for legacy journal it must exist since even for
|
||||||
|
// 0 layer it stores whether we've already generated the snapshot or are
|
||||||
|
// in progress only.
|
||||||
|
journal := rawdb.ReadSnapshotJournal(db)
|
||||||
|
if len(journal) == 0 {
|
||||||
|
return nil, journalGenerator{}, errors.New("missing or corrupted snapshot journal")
|
||||||
|
}
|
||||||
|
r := rlp.NewStream(bytes.NewReader(journal), 0)
|
||||||
|
|
||||||
|
// Read the snapshot generation progress for the disk layer
|
||||||
|
var generator journalGenerator
|
||||||
|
if err := r.Decode(&generator); err != nil {
|
||||||
|
return nil, journalGenerator{}, fmt.Errorf("failed to load snapshot progress marker: %v", err)
|
||||||
|
}
|
||||||
|
// Load all the snapshot diffs from the journal
|
||||||
|
snapshot, err := loadDiffLayer(base, r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, generator, err
|
||||||
|
}
|
||||||
|
return snapshot, generator, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadAndParseJournal tries to parse the snapshot journal in latest format.
|
||||||
|
func loadAndParseJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, journalGenerator, error) {
|
||||||
|
// Retrieve the disk layer generator. It must exist, no matter the
|
||||||
|
// snapshot is fully generated or not. Otherwise the entire disk
|
||||||
|
// layer is invalid.
|
||||||
|
generatorBlob := rawdb.ReadSnapshotGenerator(db)
|
||||||
|
if len(generatorBlob) == 0 {
|
||||||
|
return nil, journalGenerator{}, errors.New("missing snapshot generator")
|
||||||
|
}
|
||||||
|
var generator journalGenerator
|
||||||
|
if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil {
|
||||||
|
return nil, journalGenerator{}, fmt.Errorf("failed to decode snapshot generator: %v", err)
|
||||||
|
}
|
||||||
|
// Retrieve the diff layer journal. It's possible that the journal is
|
||||||
|
// not existent, e.g. the disk layer is generating while that the Geth
|
||||||
|
// crashes without persisting the diff journal.
|
||||||
|
// So if there is no journal, or the journal is invalid(e.g. the journal
|
||||||
|
// is not matched with disk layer; or the it's the legacy-format journal,
|
||||||
|
// etc.), we just discard all diffs and try to recover them later.
|
||||||
|
journal := rawdb.ReadSnapshotJournal(db)
|
||||||
|
if len(journal) == 0 {
|
||||||
|
log.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "missing")
|
||||||
|
return base, generator, nil
|
||||||
|
}
|
||||||
|
r := rlp.NewStream(bytes.NewReader(journal), 0)
|
||||||
|
|
||||||
|
// Firstly, resolve the first element as the journal version
|
||||||
|
version, err := r.Uint()
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Failed to resolve the journal version", "error", err)
|
||||||
|
return base, generator, nil
|
||||||
|
}
|
||||||
|
if version != journalVersion {
|
||||||
|
log.Warn("Discarded the snapshot journal with wrong version", "required", journalVersion, "got", version)
|
||||||
|
return base, generator, nil
|
||||||
|
}
|
||||||
|
// Secondly, resolve the disk layer root, ensure it's continuous
|
||||||
|
// with disk layer. Note now we can ensure it's the snapshot journal
|
||||||
|
// correct version, so we expect everything can be resolved properly.
|
||||||
|
var root common.Hash
|
||||||
|
if err := r.Decode(&root); err != nil {
|
||||||
|
return nil, journalGenerator{}, errors.New("missing disk layer root")
|
||||||
|
}
|
||||||
|
// The diff journal is not matched with disk, discard them.
|
||||||
|
// It can happen that Geth crashes without persisting the latest
|
||||||
|
// diff journal.
|
||||||
|
if !bytes.Equal(root.Bytes(), base.root.Bytes()) {
|
||||||
|
log.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "unmatched")
|
||||||
|
return base, generator, nil
|
||||||
|
}
|
||||||
|
// Load all the snapshot diffs from the journal
|
||||||
|
snapshot, err := loadDiffLayer(base, r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, journalGenerator{}, err
|
||||||
|
}
|
||||||
|
log.Debug("Loaded snapshot journal", "diskroot", base.root, "diffhead", snapshot.Root())
|
||||||
|
return snapshot, generator, nil
|
||||||
|
}
|
||||||
|
|
||||||
// loadSnapshot loads a pre-existing state snapshot backed by a key-value store.
|
// loadSnapshot loads a pre-existing state snapshot backed by a key-value store.
|
||||||
func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash) (snapshot, error) {
|
func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, recovery bool) (snapshot, error) {
|
||||||
// Retrieve the block number and hash of the snapshot, failing if no snapshot
|
// Retrieve the block number and hash of the snapshot, failing if no snapshot
|
||||||
// is present in the database (or crashed mid-update).
|
// is present in the database (or crashed mid-update).
|
||||||
baseRoot := rawdb.ReadSnapshotRoot(diskdb)
|
baseRoot := rawdb.ReadSnapshotRoot(diskdb)
|
||||||
|
|
@ -75,28 +160,36 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int,
|
||||||
cache: fastcache.New(cache * 1024 * 1024),
|
cache: fastcache.New(cache * 1024 * 1024),
|
||||||
root: baseRoot,
|
root: baseRoot,
|
||||||
}
|
}
|
||||||
// Retrieve the journal, it must exist since even for 0 layer it stores whether
|
var legacy bool
|
||||||
// we've already generated the snapshot or are in progress only
|
snapshot, generator, err := loadAndParseJournal(diskdb, base)
|
||||||
journal := rawdb.ReadSnapshotJournal(diskdb)
|
if err != nil {
|
||||||
if len(journal) == 0 {
|
log.Warn("Failed to load new-format journal", "error", err)
|
||||||
return nil, errors.New("missing or corrupted snapshot journal")
|
snapshot, generator, err = loadAndParseLegacyJournal(diskdb, base)
|
||||||
|
legacy = true
|
||||||
}
|
}
|
||||||
r := rlp.NewStream(bytes.NewReader(journal), 0)
|
|
||||||
|
|
||||||
// Read the snapshot generation progress for the disk layer
|
|
||||||
var generator journalGenerator
|
|
||||||
if err := r.Decode(&generator); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load snapshot progress marker: %v", err)
|
|
||||||
}
|
|
||||||
// Load all the snapshot diffs from the journal
|
|
||||||
snapshot, err := loadDiffLayer(base, r)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Entire snapshot journal loaded, sanity check the head and return
|
// Entire snapshot journal loaded, sanity check the head. If the loaded
|
||||||
// Journal doesn't exist, don't worry if it's not supposed to
|
// snapshot is not matched with current state root, print a warning log
|
||||||
|
// or discard the entire snapshot it's legacy snapshot.
|
||||||
|
//
|
||||||
|
// Possible scenario: Geth was crashed without persisting journal and then
|
||||||
|
// restart, the head is rewound to the point with available state(trie)
|
||||||
|
// which is below the snapshot. In this case the snapshot can be recovered
|
||||||
|
// by re-executing blocks but right now it's unavailable.
|
||||||
if head := snapshot.Root(); head != root {
|
if head := snapshot.Root(); head != root {
|
||||||
return nil, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root)
|
// If it's legacy snapshot, or it's new-format snapshot but
|
||||||
|
// it's not in recovery mode, returns the error here for
|
||||||
|
// rebuilding the entire snapshot forcibly.
|
||||||
|
if legacy || !recovery {
|
||||||
|
return nil, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root)
|
||||||
|
}
|
||||||
|
// It's in snapshot recovery, the assumption is held that
|
||||||
|
// the disk layer is always higher than chain head. It can
|
||||||
|
// be eventually recovered when the chain head beyonds the
|
||||||
|
// disk layer.
|
||||||
|
log.Warn("Snapshot is not continuous with chain", "snaproot", head, "chainroot", root)
|
||||||
}
|
}
|
||||||
// Everything loaded correctly, resume any suspended operations
|
// Everything loaded correctly, resume any suspended operations
|
||||||
if !generator.Done {
|
if !generator.Done {
|
||||||
|
|
@ -183,8 +276,8 @@ func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) {
|
||||||
return loadDiffLayer(newDiffLayer(parent, root, destructSet, accountData, storageData), r)
|
return loadDiffLayer(newDiffLayer(parent, root, destructSet, accountData, storageData), r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Journal writes the persistent layer generator stats into a buffer to be stored
|
// Journal terminates any in-progress snapshot generation, also implicitly pushing
|
||||||
// in the database as the snapshot journal.
|
// the progress into the database.
|
||||||
func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
|
func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
|
||||||
// If the snapshot is currently being generated, abort it
|
// If the snapshot is currently being generated, abort it
|
||||||
var stats *generatorStats
|
var stats *generatorStats
|
||||||
|
|
@ -203,20 +296,10 @@ func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
|
||||||
if dl.stale {
|
if dl.stale {
|
||||||
return common.Hash{}, ErrSnapshotStale
|
return common.Hash{}, ErrSnapshotStale
|
||||||
}
|
}
|
||||||
// Write out the generator marker
|
// Ensure the generator stats is written even if none was ran this cycle
|
||||||
entry := journalGenerator{
|
journalProgress(dl.diskdb, dl.genMarker, stats)
|
||||||
Done: dl.genMarker == nil,
|
|
||||||
Marker: dl.genMarker,
|
log.Debug("Journalled disk layer", "root", dl.root)
|
||||||
}
|
|
||||||
if stats != nil {
|
|
||||||
entry.Wiping = (stats.wiping != nil)
|
|
||||||
entry.Accounts = stats.accounts
|
|
||||||
entry.Slots = stats.slots
|
|
||||||
entry.Storage = uint64(stats.storage)
|
|
||||||
}
|
|
||||||
if err := rlp.Encode(buffer, entry); err != nil {
|
|
||||||
return common.Hash{}, err
|
|
||||||
}
|
|
||||||
return dl.root, nil
|
return dl.root, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,5 +349,98 @@ func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
|
||||||
if err := rlp.Encode(buffer, storage); err != nil {
|
if err := rlp.Encode(buffer, storage); err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
|
log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root())
|
||||||
|
return base, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LegacyJournal writes the persistent layer generator stats into a buffer
|
||||||
|
// to be stored in the database as the snapshot journal.
|
||||||
|
//
|
||||||
|
// Note it's the legacy version which is only used in testing right now.
|
||||||
|
func (dl *diskLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) {
|
||||||
|
// If the snapshot is currently being generated, abort it
|
||||||
|
var stats *generatorStats
|
||||||
|
if dl.genAbort != nil {
|
||||||
|
abort := make(chan *generatorStats)
|
||||||
|
dl.genAbort <- abort
|
||||||
|
|
||||||
|
if stats = <-abort; stats != nil {
|
||||||
|
stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ensure the layer didn't get stale
|
||||||
|
dl.lock.RLock()
|
||||||
|
defer dl.lock.RUnlock()
|
||||||
|
|
||||||
|
if dl.stale {
|
||||||
|
return common.Hash{}, ErrSnapshotStale
|
||||||
|
}
|
||||||
|
// Write out the generator marker
|
||||||
|
entry := journalGenerator{
|
||||||
|
Done: dl.genMarker == nil,
|
||||||
|
Marker: dl.genMarker,
|
||||||
|
}
|
||||||
|
if stats != nil {
|
||||||
|
entry.Wiping = (stats.wiping != nil)
|
||||||
|
entry.Accounts = stats.accounts
|
||||||
|
entry.Slots = stats.slots
|
||||||
|
entry.Storage = uint64(stats.storage)
|
||||||
|
}
|
||||||
|
log.Debug("Legacy journalled disk layer", "root", dl.root)
|
||||||
|
if err := rlp.Encode(buffer, entry); err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
return dl.root, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Journal writes the memory layer contents into a buffer to be stored in the
|
||||||
|
// database as the snapshot journal.
|
||||||
|
//
|
||||||
|
// Note it's the legacy version which is only used in testing right now.
|
||||||
|
func (dl *diffLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) {
|
||||||
|
// Journal the parent first
|
||||||
|
base, err := dl.parent.LegacyJournal(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
// Ensure the layer didn't get stale
|
||||||
|
dl.lock.RLock()
|
||||||
|
defer dl.lock.RUnlock()
|
||||||
|
|
||||||
|
if dl.Stale() {
|
||||||
|
return common.Hash{}, ErrSnapshotStale
|
||||||
|
}
|
||||||
|
// Everything below was journalled, persist this layer too
|
||||||
|
if err := rlp.Encode(buffer, dl.root); err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
destructs := make([]journalDestruct, 0, len(dl.destructSet))
|
||||||
|
for hash := range dl.destructSet {
|
||||||
|
destructs = append(destructs, journalDestruct{Hash: hash})
|
||||||
|
}
|
||||||
|
if err := rlp.Encode(buffer, destructs); err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
accounts := make([]journalAccount, 0, len(dl.accountData))
|
||||||
|
for hash, blob := range dl.accountData {
|
||||||
|
accounts = append(accounts, journalAccount{Hash: hash, Blob: blob})
|
||||||
|
}
|
||||||
|
if err := rlp.Encode(buffer, accounts); err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
storage := make([]journalStorage, 0, len(dl.storageData))
|
||||||
|
for hash, slots := range dl.storageData {
|
||||||
|
keys := make([]common.Hash, 0, len(slots))
|
||||||
|
vals := make([][]byte, 0, len(slots))
|
||||||
|
for key, val := range slots {
|
||||||
|
keys = append(keys, key)
|
||||||
|
vals = append(vals, val)
|
||||||
|
}
|
||||||
|
storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals})
|
||||||
|
}
|
||||||
|
if err := rlp.Encode(buffer, storage); err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
log.Debug("Legacy journalled disk layer", "root", dl.root, "parent", dl.parent.Root())
|
||||||
return base, nil
|
return base, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -86,6 +87,10 @@ var (
|
||||||
// range of accounts covered.
|
// range of accounts covered.
|
||||||
ErrNotCoveredYet = errors.New("not covered yet")
|
ErrNotCoveredYet = errors.New("not covered yet")
|
||||||
|
|
||||||
|
// ErrNotConstructed is returned if the callers want to iterate the snapshot
|
||||||
|
// while the generation is not finished yet.
|
||||||
|
ErrNotConstructed = errors.New("snapshot is not constructed")
|
||||||
|
|
||||||
// errSnapshotCycle is returned if a snapshot is attempted to be inserted
|
// errSnapshotCycle is returned if a snapshot is attempted to be inserted
|
||||||
// that forms a cycle in the snapshot tree.
|
// that forms a cycle in the snapshot tree.
|
||||||
errSnapshotCycle = errors.New("snapshot cycle")
|
errSnapshotCycle = errors.New("snapshot cycle")
|
||||||
|
|
@ -132,6 +137,10 @@ type snapshot interface {
|
||||||
// flattening everything down (bad for reorgs).
|
// flattening everything down (bad for reorgs).
|
||||||
Journal(buffer *bytes.Buffer) (common.Hash, error)
|
Journal(buffer *bytes.Buffer) (common.Hash, error)
|
||||||
|
|
||||||
|
// LegacyJournal is basically identical to Journal. it's the legacy version for
|
||||||
|
// flushing legacy journal. Now the only purpose of this function is for testing.
|
||||||
|
LegacyJournal(buffer *bytes.Buffer) (common.Hash, error)
|
||||||
|
|
||||||
// Stale return whether this layer has become stale (was flattened across) or
|
// Stale return whether this layer has become stale (was flattened across) or
|
||||||
// if it's still live.
|
// if it's still live.
|
||||||
Stale() bool
|
Stale() bool
|
||||||
|
|
@ -164,10 +173,12 @@ type Tree struct {
|
||||||
// store (with a number of memory layers from a journal), ensuring that the head
|
// store (with a number of memory layers from a journal), ensuring that the head
|
||||||
// of the snapshot matches the expected one.
|
// of the snapshot matches the expected one.
|
||||||
//
|
//
|
||||||
// If the snapshot is missing or inconsistent, the entirety is deleted and will
|
// If the snapshot is missing or the disk layer is broken, the entire is deleted
|
||||||
// be reconstructed from scratch based on the tries in the key-value store, on a
|
// and will be reconstructed from scratch based on the tries in the key-value
|
||||||
// background thread.
|
// store, on a background thread. If the memory layers from the journal is not
|
||||||
func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool) *Tree {
|
// continuous with disk layer or the journal is missing, all diffs will be discarded
|
||||||
|
// iff it's in "recovery" mode, otherwise rebuild is mandatory.
|
||||||
|
func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool, recovery bool) *Tree {
|
||||||
// Create a new, empty snapshot tree
|
// Create a new, empty snapshot tree
|
||||||
snap := &Tree{
|
snap := &Tree{
|
||||||
diskdb: diskdb,
|
diskdb: diskdb,
|
||||||
|
|
@ -179,7 +190,7 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm
|
||||||
defer snap.waitBuild()
|
defer snap.waitBuild()
|
||||||
}
|
}
|
||||||
// Attempt to load a previously persisted snapshot and rebuild one if failed
|
// Attempt to load a previously persisted snapshot and rebuild one if failed
|
||||||
head, err := loadSnapshot(diskdb, triedb, cache, root)
|
head, err := loadSnapshot(diskdb, triedb, cache, root, recovery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Failed to load snapshot, regenerating", "err", err)
|
log.Warn("Failed to load snapshot, regenerating", "err", err)
|
||||||
snap.Rebuild(root)
|
snap.Rebuild(root)
|
||||||
|
|
@ -194,7 +205,7 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitBuild blocks until the snapshot finishes rebuilding. This method is meant
|
// waitBuild blocks until the snapshot finishes rebuilding. This method is meant
|
||||||
// to be used by tests to ensure we're testing what we believe we are.
|
// to be used by tests to ensure we're testing what we believe we are.
|
||||||
func (t *Tree) waitBuild() {
|
func (t *Tree) waitBuild() {
|
||||||
// Find the rebuild termination channel
|
// Find the rebuild termination channel
|
||||||
var done chan struct{}
|
var done chan struct{}
|
||||||
|
|
@ -411,6 +422,9 @@ func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer {
|
||||||
|
|
||||||
// diffToDisk merges a bottom-most diff into the persistent disk layer underneath
|
// diffToDisk merges a bottom-most diff into the persistent disk layer underneath
|
||||||
// it. The method will panic if called onto a non-bottom-most diff layer.
|
// it. The method will panic if called onto a non-bottom-most diff layer.
|
||||||
|
//
|
||||||
|
// The disk layer persistence should be operated in an atomic way. All updates should
|
||||||
|
// be discarded if the whole transition if not finished.
|
||||||
func diffToDisk(bottom *diffLayer) *diskLayer {
|
func diffToDisk(bottom *diffLayer) *diskLayer {
|
||||||
var (
|
var (
|
||||||
base = bottom.parent.(*diskLayer)
|
base = bottom.parent.(*diskLayer)
|
||||||
|
|
@ -423,8 +437,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer {
|
||||||
base.genAbort <- abort
|
base.genAbort <- abort
|
||||||
stats = <-abort
|
stats = <-abort
|
||||||
}
|
}
|
||||||
// Start by temporarily deleting the current snapshot block marker. This
|
// Put the deletion in the batch writer, flush all updates in the final step.
|
||||||
// ensures that in the case of a crash, the entire snapshot is invalidated.
|
|
||||||
rawdb.DeleteSnapshotRoot(batch)
|
rawdb.DeleteSnapshotRoot(batch)
|
||||||
|
|
||||||
// Mark the original base as stale as we're going to create a new wrapper
|
// Mark the original base as stale as we're going to create a new wrapper
|
||||||
|
|
@ -467,12 +480,6 @@ func diffToDisk(bottom *diffLayer) *diskLayer {
|
||||||
base.cache.Set(hash[:], data)
|
base.cache.Set(hash[:], data)
|
||||||
snapshotCleanAccountWriteMeter.Mark(int64(len(data)))
|
snapshotCleanAccountWriteMeter.Mark(int64(len(data)))
|
||||||
|
|
||||||
if batch.ValueSize() > ethdb.IdealBatchSize {
|
|
||||||
if err := batch.Write(); err != nil {
|
|
||||||
log.Crit("Failed to write account snapshot", "err", err)
|
|
||||||
}
|
|
||||||
batch.Reset()
|
|
||||||
}
|
|
||||||
snapshotFlushAccountItemMeter.Mark(1)
|
snapshotFlushAccountItemMeter.Mark(1)
|
||||||
snapshotFlushAccountSizeMeter.Mark(int64(len(data)))
|
snapshotFlushAccountSizeMeter.Mark(int64(len(data)))
|
||||||
}
|
}
|
||||||
|
|
@ -501,18 +508,19 @@ func diffToDisk(bottom *diffLayer) *diskLayer {
|
||||||
snapshotFlushStorageItemMeter.Mark(1)
|
snapshotFlushStorageItemMeter.Mark(1)
|
||||||
snapshotFlushStorageSizeMeter.Mark(int64(len(data)))
|
snapshotFlushStorageSizeMeter.Mark(int64(len(data)))
|
||||||
}
|
}
|
||||||
if batch.ValueSize() > ethdb.IdealBatchSize {
|
|
||||||
if err := batch.Write(); err != nil {
|
|
||||||
log.Crit("Failed to write storage snapshot", "err", err)
|
|
||||||
}
|
|
||||||
batch.Reset()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Update the snapshot block marker and write any remainder data
|
// Update the snapshot block marker and write any remainder data
|
||||||
rawdb.WriteSnapshotRoot(batch, bottom.root)
|
rawdb.WriteSnapshotRoot(batch, bottom.root)
|
||||||
|
|
||||||
|
// Write out the generator progress marker and report
|
||||||
|
journalProgress(batch, base.genMarker, stats)
|
||||||
|
|
||||||
|
// Flush all the updates in the single db operation. Ensure the
|
||||||
|
// disk layer transition is atomic.
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
log.Crit("Failed to write leftover snapshot", "err", err)
|
log.Crit("Failed to write leftover snapshot", "err", err)
|
||||||
}
|
}
|
||||||
|
log.Debug("Journalled disk layer", "root", bottom.root, "complete", base.genMarker == nil)
|
||||||
res := &diskLayer{
|
res := &diskLayer{
|
||||||
root: bottom.root,
|
root: bottom.root,
|
||||||
cache: base.cache,
|
cache: base.cache,
|
||||||
|
|
@ -550,7 +558,21 @@ func (t *Tree) Journal(root common.Hash) (common.Hash, error) {
|
||||||
t.lock.Lock()
|
t.lock.Lock()
|
||||||
defer t.lock.Unlock()
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
// Firstly write out the metadata of journal
|
||||||
journal := new(bytes.Buffer)
|
journal := new(bytes.Buffer)
|
||||||
|
if err := rlp.Encode(journal, journalVersion); err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
diskroot := t.diskRoot()
|
||||||
|
if diskroot == (common.Hash{}) {
|
||||||
|
return common.Hash{}, errors.New("invalid disk root")
|
||||||
|
}
|
||||||
|
// Secondly write out the disk layer root, ensure the
|
||||||
|
// diff journal is continuous with disk.
|
||||||
|
if err := rlp.Encode(journal, diskroot); err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
// Finally write out the journal of each layer in reverse order.
|
||||||
base, err := snap.(snapshot).Journal(journal)
|
base, err := snap.(snapshot).Journal(journal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
|
|
@ -560,6 +582,29 @@ func (t *Tree) Journal(root common.Hash) (common.Hash, error) {
|
||||||
return base, nil
|
return base, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LegacyJournal is basically identical to Journal. it's the legacy
|
||||||
|
// version for flushing legacy journal. Now the only purpose of this
|
||||||
|
// function is for testing.
|
||||||
|
func (t *Tree) LegacyJournal(root common.Hash) (common.Hash, error) {
|
||||||
|
// Retrieve the head snapshot to journal from var snap snapshot
|
||||||
|
snap := t.Snapshot(root)
|
||||||
|
if snap == nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("snapshot [%#x] missing", root)
|
||||||
|
}
|
||||||
|
// Run the journaling
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
journal := new(bytes.Buffer)
|
||||||
|
base, err := snap.(snapshot).LegacyJournal(journal)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
// Store the journal into the database and return
|
||||||
|
rawdb.WriteSnapshotJournal(t.diskdb, journal.Bytes())
|
||||||
|
return base, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Rebuild wipes all available snapshot data from the persistent database and
|
// Rebuild wipes all available snapshot data from the persistent database and
|
||||||
// discard all caches and diff layers. Afterwards, it starts a new snapshot
|
// discard all caches and diff layers. Afterwards, it starts a new snapshot
|
||||||
// generator with the given root hash.
|
// generator with the given root hash.
|
||||||
|
|
@ -567,6 +612,10 @@ func (t *Tree) Rebuild(root common.Hash) {
|
||||||
t.lock.Lock()
|
t.lock.Lock()
|
||||||
defer t.lock.Unlock()
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
// Firstly delete any recovery flag in the database. Because now we are
|
||||||
|
// building a brand new snapshot.
|
||||||
|
rawdb.DeleteSnapshotRecoveryNumber(t.diskdb)
|
||||||
|
|
||||||
// Track whether there's a wipe currently running and keep it alive if so
|
// Track whether there's a wipe currently running and keep it alive if so
|
||||||
var wiper chan struct{}
|
var wiper chan struct{}
|
||||||
|
|
||||||
|
|
@ -609,11 +658,79 @@ func (t *Tree) Rebuild(root common.Hash) {
|
||||||
// AccountIterator creates a new account iterator for the specified root hash and
|
// AccountIterator creates a new account iterator for the specified root hash and
|
||||||
// seeks to a starting account hash.
|
// seeks to a starting account hash.
|
||||||
func (t *Tree) AccountIterator(root common.Hash, seek common.Hash) (AccountIterator, error) {
|
func (t *Tree) AccountIterator(root common.Hash, seek common.Hash) (AccountIterator, error) {
|
||||||
|
ok, err := t.generating()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return nil, ErrNotConstructed
|
||||||
|
}
|
||||||
return newFastAccountIterator(t, root, seek)
|
return newFastAccountIterator(t, root, seek)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StorageIterator creates a new storage iterator for the specified root hash and
|
// StorageIterator creates a new storage iterator for the specified root hash and
|
||||||
// account. The iterator will be move to the specific start position.
|
// account. The iterator will be move to the specific start position.
|
||||||
func (t *Tree) StorageIterator(root common.Hash, account common.Hash, seek common.Hash) (StorageIterator, error) {
|
func (t *Tree) StorageIterator(root common.Hash, account common.Hash, seek common.Hash) (StorageIterator, error) {
|
||||||
|
ok, err := t.generating()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return nil, ErrNotConstructed
|
||||||
|
}
|
||||||
return newFastStorageIterator(t, root, account, seek)
|
return newFastStorageIterator(t, root, account, seek)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// disklayer is an internal helper function to return the disk layer.
|
||||||
|
// The lock of snapTree is assumed to be held already.
|
||||||
|
func (t *Tree) disklayer() *diskLayer {
|
||||||
|
var snap snapshot
|
||||||
|
for _, s := range t.layers {
|
||||||
|
snap = s
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if snap == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch layer := snap.(type) {
|
||||||
|
case *diskLayer:
|
||||||
|
return layer
|
||||||
|
case *diffLayer:
|
||||||
|
return layer.origin
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("%T: undefined layer", snap))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// diskRoot is a internal helper function to return the disk layer root.
|
||||||
|
// The lock of snapTree is assumed to be held already.
|
||||||
|
func (t *Tree) diskRoot() common.Hash {
|
||||||
|
disklayer := t.disklayer()
|
||||||
|
if disklayer == nil {
|
||||||
|
return common.Hash{}
|
||||||
|
}
|
||||||
|
return disklayer.Root()
|
||||||
|
}
|
||||||
|
|
||||||
|
// generating is an internal helper function which reports whether the snapshot
|
||||||
|
// is still under the construction.
|
||||||
|
func (t *Tree) generating() (bool, error) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
layer := t.disklayer()
|
||||||
|
if layer == nil {
|
||||||
|
return false, errors.New("disk layer is missing")
|
||||||
|
}
|
||||||
|
layer.lock.RLock()
|
||||||
|
defer layer.lock.RUnlock()
|
||||||
|
return layer.genMarker != nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// diskRoot is a external helper function to return the disk layer root.
|
||||||
|
func (t *Tree) DiskRoot() common.Hash {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
return t.diskRoot()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,9 @@ type StateDB struct {
|
||||||
|
|
||||||
preimages map[common.Hash][]byte
|
preimages map[common.Hash][]byte
|
||||||
|
|
||||||
|
// Per-transaction access list
|
||||||
|
accessList *accessList
|
||||||
|
|
||||||
// Journal of state modifications. This is the backbone of
|
// Journal of state modifications. This is the backbone of
|
||||||
// Snapshot and RevertToSnapshot.
|
// Snapshot and RevertToSnapshot.
|
||||||
journal *journal
|
journal *journal
|
||||||
|
|
@ -129,6 +132,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
|
||||||
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(),
|
||||||
|
accessList: newAccessList(),
|
||||||
}
|
}
|
||||||
if sdb.snaps != nil {
|
if sdb.snaps != nil {
|
||||||
if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil {
|
if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil {
|
||||||
|
|
@ -178,6 +182,7 @@ func (s *StateDB) Reset(root common.Hash) error {
|
||||||
s.snapStorage = make(map[common.Hash]map[common.Hash][]byte)
|
s.snapStorage = make(map[common.Hash]map[common.Hash][]byte)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s.accessList = newAccessList()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -697,6 +702,12 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
for hash, preimage := range s.preimages {
|
for hash, preimage := range s.preimages {
|
||||||
state.preimages[hash] = preimage
|
state.preimages[hash] = preimage
|
||||||
}
|
}
|
||||||
|
// Do we need to copy the access list? In practice: No. At the start of a
|
||||||
|
// transaction, the access list is empty. In practice, we only ever copy state
|
||||||
|
// _between_ transactions/blocks, never in the middle of a transaction.
|
||||||
|
// However, it doesn't cost us much to copy an empty list, so we do it anyway
|
||||||
|
// to not blow up if we ever decide copy it in the middle of a transaction
|
||||||
|
state.accessList = s.accessList.Copy()
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -798,6 +809,7 @@ func (s *StateDB) Prepare(thash, bhash common.Hash, ti int) {
|
||||||
s.thash = thash
|
s.thash = thash
|
||||||
s.bhash = bhash
|
s.bhash = bhash
|
||||||
s.txIndex = ti
|
s.txIndex = ti
|
||||||
|
s.accessList = newAccessList()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateDB) clearJournalAndRefund() {
|
func (s *StateDB) clearJournalAndRefund() {
|
||||||
|
|
@ -869,11 +881,50 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
|
||||||
if err := s.snaps.Update(root, parent, s.snapDestructs, s.snapAccounts, s.snapStorage); err != nil {
|
if err := s.snaps.Update(root, parent, s.snapDestructs, s.snapAccounts, s.snapStorage); 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)
|
||||||
}
|
}
|
||||||
if err := s.snaps.Cap(root, 127); err != nil { // Persistent layer is 128th, the last available trie
|
// Keep 128 diff layers in the memory, persistent layer is 129th.
|
||||||
log.Warn("Failed to cap snapshot tree", "root", root, "layers", 127, "err", err)
|
// - head layer is paired with HEAD state
|
||||||
|
// - head-1 layer is paired with HEAD-1 state
|
||||||
|
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
|
||||||
|
if err := s.snaps.Cap(root, 128); err != nil {
|
||||||
|
log.Warn("Failed to cap snapshot tree", "root", root, "layers", 128, "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil
|
s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
return root, err
|
return root, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddAddressToAccessList adds the given address to the access list
|
||||||
|
func (s *StateDB) AddAddressToAccessList(addr common.Address) {
|
||||||
|
if s.accessList.AddAddress(addr) {
|
||||||
|
s.journal.append(accessListAddAccountChange{&addr})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSlotToAccessList adds the given (address, slot)-tuple to the access list
|
||||||
|
func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) {
|
||||||
|
addrMod, slotMod := s.accessList.AddSlot(addr, slot)
|
||||||
|
if addrMod {
|
||||||
|
// In practice, this should not happen, since there is no way to enter the
|
||||||
|
// scope of 'address' without having the 'address' become already added
|
||||||
|
// to the access list (via call-variant, create, etc).
|
||||||
|
// Better safe than sorry, though
|
||||||
|
s.journal.append(accessListAddAccountChange{&addr})
|
||||||
|
}
|
||||||
|
if slotMod {
|
||||||
|
s.journal.append(accessListAddSlotChange{
|
||||||
|
address: &addr,
|
||||||
|
slot: &slot,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddressInAccessList returns true if the given address is in the access list.
|
||||||
|
func (s *StateDB) AddressInAccessList(addr common.Address) bool {
|
||||||
|
return s.accessList.ContainsAddress(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SlotInAccessList returns true if the given (address, slot)-tuple is in the access list.
|
||||||
|
func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
|
||||||
|
return s.accessList.Contains(addr, slot)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -328,6 +328,20 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
|
||||||
},
|
},
|
||||||
args: make([]int64, 1),
|
args: make([]int64, 1),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "AddAddressToAccessList",
|
||||||
|
fn: func(a testAction, s *StateDB) {
|
||||||
|
s.AddAddressToAccessList(addr)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AddSlotToAccessList",
|
||||||
|
fn: func(a testAction, s *StateDB) {
|
||||||
|
s.AddSlotToAccessList(addr,
|
||||||
|
common.Hash{byte(a.args[0])})
|
||||||
|
},
|
||||||
|
args: make([]int64, 1),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
action := actions[r.Intn(len(actions))]
|
action := actions[r.Intn(len(actions))]
|
||||||
var nameargs []string
|
var nameargs []string
|
||||||
|
|
@ -727,3 +741,177 @@ func TestMissingTrieNodes(t *testing.T) {
|
||||||
t.Fatalf("expected error, got root :%x", root)
|
t.Fatalf("expected error, got root :%x", root)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStateDBAccessList(t *testing.T) {
|
||||||
|
// Some helpers
|
||||||
|
addr := func(a string) common.Address {
|
||||||
|
return common.HexToAddress(a)
|
||||||
|
}
|
||||||
|
slot := func(a string) common.Hash {
|
||||||
|
return common.HexToHash(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
memDb := rawdb.NewMemoryDatabase()
|
||||||
|
db := NewDatabase(memDb)
|
||||||
|
state, _ := New(common.Hash{}, db, nil)
|
||||||
|
state.accessList = newAccessList()
|
||||||
|
|
||||||
|
verifyAddrs := func(astrings ...string) {
|
||||||
|
t.Helper()
|
||||||
|
// convert to common.Address form
|
||||||
|
var addresses []common.Address
|
||||||
|
var addressMap = make(map[common.Address]struct{})
|
||||||
|
for _, astring := range astrings {
|
||||||
|
address := addr(astring)
|
||||||
|
addresses = append(addresses, address)
|
||||||
|
addressMap[address] = struct{}{}
|
||||||
|
}
|
||||||
|
// Check that the given addresses are in the access list
|
||||||
|
for _, address := range addresses {
|
||||||
|
if !state.AddressInAccessList(address) {
|
||||||
|
t.Fatalf("expected %x to be in access list", address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check that only the expected addresses are present in the acesslist
|
||||||
|
for address := range state.accessList.addresses {
|
||||||
|
if _, exist := addressMap[address]; !exist {
|
||||||
|
t.Fatalf("extra address %x in access list", address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verifySlots := func(addrString string, slotStrings ...string) {
|
||||||
|
if !state.AddressInAccessList(addr(addrString)) {
|
||||||
|
t.Fatalf("scope missing address/slots %v", addrString)
|
||||||
|
}
|
||||||
|
var address = addr(addrString)
|
||||||
|
// convert to common.Hash form
|
||||||
|
var slots []common.Hash
|
||||||
|
var slotMap = make(map[common.Hash]struct{})
|
||||||
|
for _, slotString := range slotStrings {
|
||||||
|
s := slot(slotString)
|
||||||
|
slots = append(slots, s)
|
||||||
|
slotMap[s] = struct{}{}
|
||||||
|
}
|
||||||
|
// Check that the expected items are in the access list
|
||||||
|
for i, s := range slots {
|
||||||
|
if _, slotPresent := state.SlotInAccessList(address, s); !slotPresent {
|
||||||
|
t.Fatalf("input %d: scope missing slot %v (address %v)", i, s, addrString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check that no extra elements are in the access list
|
||||||
|
index := state.accessList.addresses[address]
|
||||||
|
if index >= 0 {
|
||||||
|
stateSlots := state.accessList.slots[index]
|
||||||
|
for s := range stateSlots {
|
||||||
|
if _, slotPresent := slotMap[s]; !slotPresent {
|
||||||
|
t.Fatalf("scope has extra slot %v (address %v)", s, addrString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.AddAddressToAccessList(addr("aa")) // 1
|
||||||
|
state.AddSlotToAccessList(addr("bb"), slot("01")) // 2,3
|
||||||
|
state.AddSlotToAccessList(addr("bb"), slot("02")) // 4
|
||||||
|
verifyAddrs("aa", "bb")
|
||||||
|
verifySlots("bb", "01", "02")
|
||||||
|
|
||||||
|
// Make a copy
|
||||||
|
stateCopy1 := state.Copy()
|
||||||
|
if exp, got := 4, state.journal.length(); exp != got {
|
||||||
|
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// same again, should cause no journal entries
|
||||||
|
state.AddSlotToAccessList(addr("bb"), slot("01"))
|
||||||
|
state.AddSlotToAccessList(addr("bb"), slot("02"))
|
||||||
|
state.AddAddressToAccessList(addr("aa"))
|
||||||
|
if exp, got := 4, state.journal.length(); exp != got {
|
||||||
|
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
|
||||||
|
}
|
||||||
|
// some new ones
|
||||||
|
state.AddSlotToAccessList(addr("bb"), slot("03")) // 5
|
||||||
|
state.AddSlotToAccessList(addr("aa"), slot("01")) // 6
|
||||||
|
state.AddSlotToAccessList(addr("cc"), slot("01")) // 7,8
|
||||||
|
state.AddAddressToAccessList(addr("cc"))
|
||||||
|
if exp, got := 8, state.journal.length(); exp != got {
|
||||||
|
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAddrs("aa", "bb", "cc")
|
||||||
|
verifySlots("aa", "01")
|
||||||
|
verifySlots("bb", "01", "02", "03")
|
||||||
|
verifySlots("cc", "01")
|
||||||
|
|
||||||
|
// now start rolling back changes
|
||||||
|
state.journal.revert(state, 7)
|
||||||
|
if _, ok := state.SlotInAccessList(addr("cc"), slot("01")); ok {
|
||||||
|
t.Fatalf("slot present, expected missing")
|
||||||
|
}
|
||||||
|
verifyAddrs("aa", "bb", "cc")
|
||||||
|
verifySlots("aa", "01")
|
||||||
|
verifySlots("bb", "01", "02", "03")
|
||||||
|
|
||||||
|
state.journal.revert(state, 6)
|
||||||
|
if state.AddressInAccessList(addr("cc")) {
|
||||||
|
t.Fatalf("addr present, expected missing")
|
||||||
|
}
|
||||||
|
verifyAddrs("aa", "bb")
|
||||||
|
verifySlots("aa", "01")
|
||||||
|
verifySlots("bb", "01", "02", "03")
|
||||||
|
|
||||||
|
state.journal.revert(state, 5)
|
||||||
|
if _, ok := state.SlotInAccessList(addr("aa"), slot("01")); ok {
|
||||||
|
t.Fatalf("slot present, expected missing")
|
||||||
|
}
|
||||||
|
verifyAddrs("aa", "bb")
|
||||||
|
verifySlots("bb", "01", "02", "03")
|
||||||
|
|
||||||
|
state.journal.revert(state, 4)
|
||||||
|
if _, ok := state.SlotInAccessList(addr("bb"), slot("03")); ok {
|
||||||
|
t.Fatalf("slot present, expected missing")
|
||||||
|
}
|
||||||
|
verifyAddrs("aa", "bb")
|
||||||
|
verifySlots("bb", "01", "02")
|
||||||
|
|
||||||
|
state.journal.revert(state, 3)
|
||||||
|
if _, ok := state.SlotInAccessList(addr("bb"), slot("02")); ok {
|
||||||
|
t.Fatalf("slot present, expected missing")
|
||||||
|
}
|
||||||
|
verifyAddrs("aa", "bb")
|
||||||
|
verifySlots("bb", "01")
|
||||||
|
|
||||||
|
state.journal.revert(state, 2)
|
||||||
|
if _, ok := state.SlotInAccessList(addr("bb"), slot("01")); ok {
|
||||||
|
t.Fatalf("slot present, expected missing")
|
||||||
|
}
|
||||||
|
verifyAddrs("aa", "bb")
|
||||||
|
|
||||||
|
state.journal.revert(state, 1)
|
||||||
|
if state.AddressInAccessList(addr("bb")) {
|
||||||
|
t.Fatalf("addr present, expected missing")
|
||||||
|
}
|
||||||
|
verifyAddrs("aa")
|
||||||
|
|
||||||
|
state.journal.revert(state, 0)
|
||||||
|
if state.AddressInAccessList(addr("aa")) {
|
||||||
|
t.Fatalf("addr present, expected missing")
|
||||||
|
}
|
||||||
|
if got, exp := len(state.accessList.addresses), 0; got != exp {
|
||||||
|
t.Fatalf("expected empty, got %d", got)
|
||||||
|
}
|
||||||
|
if got, exp := len(state.accessList.slots), 0; got != exp {
|
||||||
|
t.Fatalf("expected empty, got %d", got)
|
||||||
|
}
|
||||||
|
// Check the copy
|
||||||
|
// Make a copy
|
||||||
|
state = stateCopy1
|
||||||
|
verifyAddrs("aa", "bb")
|
||||||
|
verifySlots("bb", "01", "02")
|
||||||
|
if got, exp := len(state.accessList.addresses), 2; got != exp {
|
||||||
|
t.Fatalf("expected empty, got %d", got)
|
||||||
|
}
|
||||||
|
if got, exp := len(state.accessList.slots), 1; got != exp {
|
||||||
|
t.Fatalf("expected empty, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,18 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
|
||||||
// Create a new environment which holds all relevant information
|
// Create a new environment which holds all relevant information
|
||||||
// about the transaction and calling mechanisms.
|
// about the transaction and calling mechanisms.
|
||||||
vmenv := vm.NewEVM(context, statedb, config, cfg)
|
vmenv := vm.NewEVM(context, statedb, config, cfg)
|
||||||
|
|
||||||
|
if config.IsYoloV2(header.Number) {
|
||||||
|
statedb.AddAddressToAccessList(msg.From())
|
||||||
|
if dst := msg.To(); dst != nil {
|
||||||
|
statedb.AddAddressToAccessList(*dst)
|
||||||
|
// If it's a create-tx, the destination will be added inside evm.create
|
||||||
|
}
|
||||||
|
for _, addr := range vmenv.ActivePrecompiles() {
|
||||||
|
statedb.AddAddressToAccessList(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Apply the transaction to the current state (included in the env)
|
// Apply the transaction to the current state (included in the env)
|
||||||
result, err := ApplyMessage(vmenv, msg, gp)
|
result, err := ApplyMessage(vmenv, msg, gp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,6 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
@ -37,26 +35,24 @@ type Hasher interface {
|
||||||
|
|
||||||
func DeriveSha(list DerivableList, hasher Hasher) common.Hash {
|
func DeriveSha(list DerivableList, hasher Hasher) common.Hash {
|
||||||
hasher.Reset()
|
hasher.Reset()
|
||||||
keybuf := new(bytes.Buffer)
|
|
||||||
|
|
||||||
// StackTrie requires values to be inserted in increasing
|
// StackTrie requires values to be inserted in increasing
|
||||||
// hash order, which is not the order that `list` provides
|
// hash order, which is not the order that `list` provides
|
||||||
// hashes in. This insertion sequence ensures that the
|
// hashes in. This insertion sequence ensures that the
|
||||||
// order is correct.
|
// order is correct.
|
||||||
|
|
||||||
|
var buf []byte
|
||||||
for i := 1; i < list.Len() && i <= 0x7f; i++ {
|
for i := 1; i < list.Len() && i <= 0x7f; i++ {
|
||||||
keybuf.Reset()
|
buf = rlp.AppendUint64(buf[:0], uint64(i))
|
||||||
rlp.Encode(keybuf, uint(i))
|
hasher.Update(buf, list.GetRlp(i))
|
||||||
hasher.Update(keybuf.Bytes(), list.GetRlp(i))
|
|
||||||
}
|
}
|
||||||
if list.Len() > 0 {
|
if list.Len() > 0 {
|
||||||
keybuf.Reset()
|
buf = rlp.AppendUint64(buf[:0], 0)
|
||||||
rlp.Encode(keybuf, uint(0))
|
hasher.Update(buf, list.GetRlp(0))
|
||||||
hasher.Update(keybuf.Bytes(), list.GetRlp(0))
|
|
||||||
}
|
}
|
||||||
for i := 0x80; i < list.Len(); i++ {
|
for i := 0x80; i < list.Len(); i++ {
|
||||||
keybuf.Reset()
|
buf = rlp.AppendUint64(buf[:0], uint64(i))
|
||||||
rlp.Encode(keybuf, uint(i))
|
hasher.Update(buf, list.GetRlp(i))
|
||||||
hasher.Update(keybuf.Bytes(), list.GetRlp(i))
|
|
||||||
}
|
}
|
||||||
return hasher.Hash()
|
return hasher.Hash()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,9 +78,9 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
|
||||||
common.BytesToAddress([]byte{9}): &blake2F{},
|
common.BytesToAddress([]byte{9}): &blake2F{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrecompiledContractsYoloV1 contains the default set of pre-compiled Ethereum
|
// PrecompiledContractsYoloV2 contains the default set of pre-compiled Ethereum
|
||||||
// contracts used in the Yolo v1 test release.
|
// contracts used in the Yolo v2 test release.
|
||||||
var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{
|
var PrecompiledContractsYoloV2 = map[common.Address]PrecompiledContract{
|
||||||
common.BytesToAddress([]byte{1}): &ecrecover{},
|
common.BytesToAddress([]byte{1}): &ecrecover{},
|
||||||
common.BytesToAddress([]byte{2}): &sha256hash{},
|
common.BytesToAddress([]byte{2}): &sha256hash{},
|
||||||
common.BytesToAddress([]byte{3}): &ripemd160hash{},
|
common.BytesToAddress([]byte{3}): &ripemd160hash{},
|
||||||
|
|
@ -101,6 +101,28 @@ var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{
|
||||||
common.BytesToAddress([]byte{18}): &bls12381MapG2{},
|
common.BytesToAddress([]byte{18}): &bls12381MapG2{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
PrecompiledAddressesYoloV2 []common.Address
|
||||||
|
PrecompiledAddressesIstanbul []common.Address
|
||||||
|
PrecompiledAddressesByzantium []common.Address
|
||||||
|
PrecompiledAddressesHomestead []common.Address
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for k := range PrecompiledContractsHomestead {
|
||||||
|
PrecompiledAddressesHomestead = append(PrecompiledAddressesHomestead, k)
|
||||||
|
}
|
||||||
|
for k := range PrecompiledContractsByzantium {
|
||||||
|
PrecompiledAddressesHomestead = append(PrecompiledAddressesByzantium, k)
|
||||||
|
}
|
||||||
|
for k := range PrecompiledContractsIstanbul {
|
||||||
|
PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k)
|
||||||
|
}
|
||||||
|
for k := range PrecompiledContractsYoloV2 {
|
||||||
|
PrecompiledAddressesYoloV2 = append(PrecompiledAddressesYoloV2, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
|
// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
|
||||||
// It returns
|
// It returns
|
||||||
// - the returned bytes,
|
// - the returned bytes,
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ type precompiledFailureTest struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
var allPrecompiles = PrecompiledContractsYoloV1
|
var allPrecompiles = PrecompiledContractsYoloV2
|
||||||
|
|
||||||
// EIP-152 test vectors
|
// EIP-152 test vectors
|
||||||
var blake2FMalformedInputTests = []precompiledFailureTest{
|
var blake2FMalformedInputTests = []precompiledFailureTest{
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var activators = map[int]func(*JumpTable){
|
var activators = map[int]func(*JumpTable){
|
||||||
|
2929: enable2929,
|
||||||
2200: enable2200,
|
2200: enable2200,
|
||||||
1884: enable1884,
|
1884: enable1884,
|
||||||
1344: enable1344,
|
1344: enable1344,
|
||||||
|
|
@ -134,3 +135,41 @@ func enable2315(jt *JumpTable) {
|
||||||
jumps: true,
|
jumps: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enable2929 enables "EIP-2929: Gas cost increases for state access opcodes"
|
||||||
|
// https://eips.ethereum.org/EIPS/eip-2929
|
||||||
|
func enable2929(jt *JumpTable) {
|
||||||
|
jt[SSTORE].dynamicGas = gasSStoreEIP2929
|
||||||
|
|
||||||
|
jt[SLOAD].constantGas = 0
|
||||||
|
jt[SLOAD].dynamicGas = gasSLoadEIP2929
|
||||||
|
|
||||||
|
jt[EXTCODECOPY].constantGas = WarmStorageReadCostEIP2929
|
||||||
|
jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929
|
||||||
|
|
||||||
|
jt[EXTCODESIZE].constantGas = WarmStorageReadCostEIP2929
|
||||||
|
jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheck
|
||||||
|
|
||||||
|
jt[EXTCODEHASH].constantGas = WarmStorageReadCostEIP2929
|
||||||
|
jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheck
|
||||||
|
|
||||||
|
jt[BALANCE].constantGas = WarmStorageReadCostEIP2929
|
||||||
|
jt[BALANCE].dynamicGas = gasEip2929AccountCheck
|
||||||
|
|
||||||
|
jt[CALL].constantGas = WarmStorageReadCostEIP2929
|
||||||
|
jt[CALL].dynamicGas = gasCallEIP2929
|
||||||
|
|
||||||
|
jt[CALLCODE].constantGas = WarmStorageReadCostEIP2929
|
||||||
|
jt[CALLCODE].dynamicGas = gasCallCodeEIP2929
|
||||||
|
|
||||||
|
jt[STATICCALL].constantGas = WarmStorageReadCostEIP2929
|
||||||
|
jt[STATICCALL].dynamicGas = gasStaticCallEIP2929
|
||||||
|
|
||||||
|
jt[DELEGATECALL].constantGas = WarmStorageReadCostEIP2929
|
||||||
|
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929
|
||||||
|
|
||||||
|
// This was previously part of the dynamic cost, but we're using it as a constantGas
|
||||||
|
// factor here
|
||||||
|
jt[SELFDESTRUCT].constantGas = params.SelfdestructGasEIP150
|
||||||
|
jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,11 +42,26 @@ type (
|
||||||
GetHashFunc func(uint64) common.Hash
|
GetHashFunc func(uint64) common.Hash
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ActivePrecompiles returns the addresses of the precompiles enabled with the current
|
||||||
|
// configuration
|
||||||
|
func (evm *EVM) ActivePrecompiles() []common.Address {
|
||||||
|
switch {
|
||||||
|
case evm.chainRules.IsYoloV2:
|
||||||
|
return PrecompiledAddressesYoloV2
|
||||||
|
case evm.chainRules.IsIstanbul:
|
||||||
|
return PrecompiledAddressesIstanbul
|
||||||
|
case evm.chainRules.IsByzantium:
|
||||||
|
return PrecompiledAddressesByzantium
|
||||||
|
default:
|
||||||
|
return PrecompiledAddressesHomestead
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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.IsYoloV1:
|
case evm.chainRules.IsYoloV2:
|
||||||
precompiles = PrecompiledContractsYoloV1
|
precompiles = PrecompiledContractsYoloV2
|
||||||
case evm.chainRules.IsIstanbul:
|
case evm.chainRules.IsIstanbul:
|
||||||
precompiles = PrecompiledContractsIstanbul
|
precompiles = PrecompiledContractsIstanbul
|
||||||
case evm.chainRules.IsByzantium:
|
case evm.chainRules.IsByzantium:
|
||||||
|
|
@ -416,7 +431,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||||
}
|
}
|
||||||
nonce := evm.StateDB.GetNonce(caller.Address())
|
nonce := evm.StateDB.GetNonce(caller.Address())
|
||||||
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
|
||||||
|
if evm.chainRules.IsYoloV2 {
|
||||||
|
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
|
||||||
contractHash := evm.StateDB.GetCodeHash(address)
|
contractHash := evm.StateDB.GetCodeHash(address)
|
||||||
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) {
|
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
|
||||||
MemorySize int `json:"memSize"`
|
MemorySize int `json:"memSize"`
|
||||||
Stack []*math.HexOrDecimal256 `json:"stack"`
|
Stack []*math.HexOrDecimal256 `json:"stack"`
|
||||||
ReturnStack []math.HexOrDecimal64 `json:"returnStack"`
|
ReturnStack []math.HexOrDecimal64 `json:"returnStack"`
|
||||||
ReturnData []byte `json:"returnData"`
|
ReturnData hexutil.Bytes `json:"returnData"`
|
||||||
Storage map[common.Hash]common.Hash `json:"-"`
|
Storage map[common.Hash]common.Hash `json:"-"`
|
||||||
Depth int `json:"depth"`
|
Depth int `json:"depth"`
|
||||||
RefundCounter uint64 `json:"refund"`
|
RefundCounter uint64 `json:"refund"`
|
||||||
|
|
@ -72,7 +72,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
|
||||||
MemorySize *int `json:"memSize"`
|
MemorySize *int `json:"memSize"`
|
||||||
Stack []*math.HexOrDecimal256 `json:"stack"`
|
Stack []*math.HexOrDecimal256 `json:"stack"`
|
||||||
ReturnStack []math.HexOrDecimal64 `json:"returnStack"`
|
ReturnStack []math.HexOrDecimal64 `json:"returnStack"`
|
||||||
ReturnData []byte `json:"returnData"`
|
ReturnData *hexutil.Bytes `json:"returnData"`
|
||||||
Storage map[common.Hash]common.Hash `json:"-"`
|
Storage map[common.Hash]common.Hash `json:"-"`
|
||||||
Depth *int `json:"depth"`
|
Depth *int `json:"depth"`
|
||||||
RefundCounter *uint64 `json:"refund"`
|
RefundCounter *uint64 `json:"refund"`
|
||||||
|
|
@ -113,7 +113,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if dec.ReturnData != nil {
|
if dec.ReturnData != nil {
|
||||||
s.ReturnData = dec.ReturnData
|
s.ReturnData = *dec.ReturnData
|
||||||
}
|
}
|
||||||
if dec.Storage != nil {
|
if dec.Storage != nil {
|
||||||
s.Storage = dec.Storage
|
s.Storage = dec.Storage
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,15 @@ type StateDB interface {
|
||||||
// is defined according to EIP161 (balance = nonce = code = 0).
|
// is defined according to EIP161 (balance = nonce = code = 0).
|
||||||
Empty(common.Address) bool
|
Empty(common.Address) bool
|
||||||
|
|
||||||
|
AddressInAccessList(addr common.Address) bool
|
||||||
|
SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
|
||||||
|
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
|
||||||
|
// even if the feature/fork is not active yet
|
||||||
|
AddAddressToAccessList(addr common.Address)
|
||||||
|
// 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
|
||||||
|
AddSlotToAccessList(addr common.Address, slot common.Hash)
|
||||||
|
|
||||||
RevertToSnapshot(int)
|
RevertToSnapshot(int)
|
||||||
Snapshot() int
|
Snapshot() int
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,8 +99,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
|
||||||
if cfg.JumpTable[STOP] == nil {
|
if cfg.JumpTable[STOP] == nil {
|
||||||
var jt JumpTable
|
var jt JumpTable
|
||||||
switch {
|
switch {
|
||||||
case evm.chainRules.IsYoloV1:
|
case evm.chainRules.IsYoloV2:
|
||||||
jt = yoloV1InstructionSet
|
jt = yoloV2InstructionSet
|
||||||
case evm.chainRules.IsIstanbul:
|
case evm.chainRules.IsIstanbul:
|
||||||
jt = istanbulInstructionSet
|
jt = istanbulInstructionSet
|
||||||
case evm.chainRules.IsConstantinople:
|
case evm.chainRules.IsConstantinople:
|
||||||
|
|
|
||||||
|
|
@ -56,17 +56,19 @@ var (
|
||||||
byzantiumInstructionSet = newByzantiumInstructionSet()
|
byzantiumInstructionSet = newByzantiumInstructionSet()
|
||||||
constantinopleInstructionSet = newConstantinopleInstructionSet()
|
constantinopleInstructionSet = newConstantinopleInstructionSet()
|
||||||
istanbulInstructionSet = newIstanbulInstructionSet()
|
istanbulInstructionSet = newIstanbulInstructionSet()
|
||||||
yoloV1InstructionSet = newYoloV1InstructionSet()
|
yoloV2InstructionSet = newYoloV2InstructionSet()
|
||||||
)
|
)
|
||||||
|
|
||||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||||
type JumpTable [256]*operation
|
type JumpTable [256]*operation
|
||||||
|
|
||||||
func newYoloV1InstructionSet() JumpTable {
|
// newYoloV2InstructionSet creates an instructionset containing
|
||||||
|
// - "EIP-2315: Simple Subroutines"
|
||||||
|
// - "EIP-2929: Gas cost increases for state access opcodes"
|
||||||
|
func newYoloV2InstructionSet() JumpTable {
|
||||||
instructionSet := newIstanbulInstructionSet()
|
instructionSet := newIstanbulInstructionSet()
|
||||||
|
|
||||||
enable2315(&instructionSet) // Subroutines - https://eips.ethereum.org/EIPS/eip-2315
|
enable2315(&instructionSet) // Subroutines - https://eips.ethereum.org/EIPS/eip-2315
|
||||||
|
enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929
|
||||||
return instructionSet
|
return instructionSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errTraceLimitReached = errors.New("the number of logs reached the specified limit")
|
var errTraceLimitReached = errors.New("the number of logs reached the specified limit")
|
||||||
|
|
@ -53,6 +54,8 @@ type LogConfig struct {
|
||||||
DisableReturnData bool // disable return data capture
|
DisableReturnData bool // disable return data capture
|
||||||
Debug bool // print output during capture end
|
Debug bool // print output during capture end
|
||||||
Limit int // maximum length of output, but zero means unlimited
|
Limit int // maximum length of output, but zero means unlimited
|
||||||
|
// Chain overrides, can be used to execute a trace using future fork rules
|
||||||
|
Overrides *params.ChainConfig `json:"overrides,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
|
//go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
|
||||||
|
|
@ -82,6 +85,7 @@ type structLogMarshaling struct {
|
||||||
Gas math.HexOrDecimal64
|
Gas math.HexOrDecimal64
|
||||||
GasCost math.HexOrDecimal64
|
GasCost math.HexOrDecimal64
|
||||||
Memory hexutil.Bytes
|
Memory hexutil.Bytes
|
||||||
|
ReturnData hexutil.Bytes
|
||||||
OpName string `json:"opName"` // adds call to OpName() in MarshalJSON
|
OpName string `json:"opName"` // adds call to OpName() in MarshalJSON
|
||||||
ErrorString string `json:"error"` // adds call to ErrorString() in MarshalJSON
|
ErrorString string `json:"error"` // adds call to ErrorString() in MarshalJSON
|
||||||
}
|
}
|
||||||
|
|
@ -313,8 +317,8 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(t.out, `
|
fmt.Fprintf(t.out, `
|
||||||
| Pc | Op | Cost | Stack | RStack |
|
| Pc | Op | Cost | Stack | RStack | Refund |
|
||||||
|-------|-------------|------|-----------|-----------|
|
|-------|-------------|------|-----------|-----------|---------|
|
||||||
`)
|
`)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -326,7 +330,7 @@ func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64
|
||||||
// format stack
|
// format stack
|
||||||
var a []string
|
var a []string
|
||||||
for _, elem := range stack.data {
|
for _, elem := range stack.data {
|
||||||
a = append(a, fmt.Sprintf("%d", elem))
|
a = append(a, fmt.Sprintf("%v", elem.String()))
|
||||||
}
|
}
|
||||||
b := fmt.Sprintf("[%v]", strings.Join(a, ","))
|
b := fmt.Sprintf("[%v]", strings.Join(a, ","))
|
||||||
fmt.Fprintf(t.out, "%10v |", b)
|
fmt.Fprintf(t.out, "%10v |", b)
|
||||||
|
|
@ -339,6 +343,7 @@ func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64
|
||||||
b = fmt.Sprintf("[%v]", strings.Join(a, ","))
|
b = fmt.Sprintf("[%v]", strings.Join(a, ","))
|
||||||
fmt.Fprintf(t.out, "%10v |", b)
|
fmt.Fprintf(t.out, "%10v |", b)
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(t.out, "%10v |", env.StateDB.GetRefund())
|
||||||
fmt.Fprintln(t.out, "")
|
fmt.Fprintln(t.out, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(t.out, "Error: %v\n", err)
|
fmt.Fprintf(t.out, "Error: %v\n", err)
|
||||||
|
|
@ -354,11 +359,7 @@ func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) error {
|
func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) error {
|
||||||
fmt.Fprintf(t.out, `
|
fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n",
|
||||||
Output: 0x%x
|
|
||||||
Consumed gas: %d
|
|
||||||
Error: %v
|
|
||||||
`,
|
|
||||||
output, gasUsed, err)
|
output, gasUsed, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
222
core/vm/operations_acl.go
Normal file
222
core/vm/operations_acl.go
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
// Copyright 2020 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 (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ColdAccountAccessCostEIP2929 = uint64(2600) // COLD_ACCOUNT_ACCESS_COST
|
||||||
|
ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST
|
||||||
|
WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST
|
||||||
|
)
|
||||||
|
|
||||||
|
// gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929"
|
||||||
|
//
|
||||||
|
// When calling SSTORE, check if the (address, storage_key) pair is in accessed_storage_keys.
|
||||||
|
// If it is not, charge an additional COLD_SLOAD_COST gas, and add the pair to accessed_storage_keys.
|
||||||
|
// Additionally, modify the parameters defined in EIP 2200 as follows:
|
||||||
|
//
|
||||||
|
// Parameter Old value New value
|
||||||
|
// SLOAD_GAS 800 = WARM_STORAGE_READ_COST
|
||||||
|
// SSTORE_RESET_GAS 5000 5000 - COLD_SLOAD_COST
|
||||||
|
//
|
||||||
|
//The other parameters defined in EIP 2200 are unchanged.
|
||||||
|
// see gasSStoreEIP2200(...) in core/vm/gas_table.go for more info about how EIP 2200 is specified
|
||||||
|
func gasSStoreEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
// If we fail the minimum gas availability invariant, fail (0)
|
||||||
|
if contract.Gas <= params.SstoreSentryGasEIP2200 {
|
||||||
|
return 0, errors.New("not enough gas for reentrancy sentry")
|
||||||
|
}
|
||||||
|
// Gas sentry honoured, do the actual gas calculation based on the stored value
|
||||||
|
var (
|
||||||
|
y, x = stack.Back(1), stack.peek()
|
||||||
|
slot = common.Hash(x.Bytes32())
|
||||||
|
current = evm.StateDB.GetState(contract.Address(), slot)
|
||||||
|
cost = uint64(0)
|
||||||
|
)
|
||||||
|
// Check slot presence in the access list
|
||||||
|
if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
|
||||||
|
cost = ColdSloadCostEIP2929
|
||||||
|
// If the caller cannot afford the cost, this change will be rolled back
|
||||||
|
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||||
|
if !addrPresent {
|
||||||
|
// Once we're done with YOLOv2 and schedule this for mainnet, might
|
||||||
|
// be good to remove this panic here, which is just really a
|
||||||
|
// canary to have during testing
|
||||||
|
panic("impossible case: address was not present in access list during sstore op")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value := common.Hash(y.Bytes32())
|
||||||
|
|
||||||
|
if current == value { // noop (1)
|
||||||
|
// EIP 2200 original clause:
|
||||||
|
// return params.SloadGasEIP2200, nil
|
||||||
|
return cost + WarmStorageReadCostEIP2929, nil // SLOAD_GAS
|
||||||
|
}
|
||||||
|
original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32()))
|
||||||
|
if original == current {
|
||||||
|
if original == (common.Hash{}) { // create slot (2.1.1)
|
||||||
|
return cost + params.SstoreSetGasEIP2200, nil
|
||||||
|
}
|
||||||
|
if value == (common.Hash{}) { // delete slot (2.1.2b)
|
||||||
|
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200)
|
||||||
|
}
|
||||||
|
// EIP-2200 original clause:
|
||||||
|
// return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2)
|
||||||
|
return cost + (params.SstoreResetGasEIP2200 - ColdSloadCostEIP2929), nil // write existing slot (2.1.2)
|
||||||
|
}
|
||||||
|
if original != (common.Hash{}) {
|
||||||
|
if current == (common.Hash{}) { // recreate slot (2.2.1.1)
|
||||||
|
evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP2200)
|
||||||
|
} else if value == (common.Hash{}) { // delete slot (2.2.1.2)
|
||||||
|
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if original == value {
|
||||||
|
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
|
||||||
|
// EIP 2200 Original clause:
|
||||||
|
//evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200)
|
||||||
|
evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - WarmStorageReadCostEIP2929)
|
||||||
|
} else { // reset to original existing slot (2.2.2.2)
|
||||||
|
// EIP 2200 Original clause:
|
||||||
|
// evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200)
|
||||||
|
// - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST)
|
||||||
|
// - SLOAD_GAS redefined as WARM_STORAGE_READ_COST
|
||||||
|
// Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST
|
||||||
|
evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - ColdSloadCostEIP2929) - WarmStorageReadCostEIP2929)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// EIP-2200 original clause:
|
||||||
|
//return params.SloadGasEIP2200, nil // dirty update (2.2)
|
||||||
|
return cost + WarmStorageReadCostEIP2929, nil // dirty update (2.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// gasSLoadEIP2929 calculates dynamic gas for SLOAD according to EIP-2929
|
||||||
|
// For SLOAD, if the (address, storage_key) pair (where address is the address of the contract
|
||||||
|
// whose storage is being read) is not yet in accessed_storage_keys,
|
||||||
|
// charge 2100 gas and add the pair to accessed_storage_keys.
|
||||||
|
// If the pair is already in accessed_storage_keys, charge 100 gas.
|
||||||
|
func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
loc := stack.peek()
|
||||||
|
slot := common.Hash(loc.Bytes32())
|
||||||
|
// Check slot presence in the access list
|
||||||
|
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
|
||||||
|
// If the caller cannot afford the cost, this change will be rolled back
|
||||||
|
// If he does afford it, we can skip checking the same thing later on, during execution
|
||||||
|
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||||
|
return ColdSloadCostEIP2929, nil
|
||||||
|
}
|
||||||
|
return WarmStorageReadCostEIP2929, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929
|
||||||
|
// EIP spec:
|
||||||
|
// > If the target is not in accessed_addresses,
|
||||||
|
// > charge COLD_ACCOUNT_ACCESS_COST gas, and add the address to accessed_addresses.
|
||||||
|
// > Otherwise, charge WARM_STORAGE_READ_COST gas.
|
||||||
|
func gasExtCodeCopyEIP2929(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())
|
||||||
|
// Check slot presence in the access list
|
||||||
|
if !evm.StateDB.AddressInAccessList(addr) {
|
||||||
|
evm.StateDB.AddAddressToAccessList(addr)
|
||||||
|
var overflow bool
|
||||||
|
// We charge (cold-warm), since 'warm' is already charged as constantGas
|
||||||
|
if gas, overflow = math.SafeAdd(gas, ColdAccountAccessCostEIP2929-WarmStorageReadCostEIP2929); overflow {
|
||||||
|
return 0, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// gasEip2929AccountCheck checks whether the first stack item (as address) is present in the access list.
|
||||||
|
// If it is, this method returns '0', otherwise 'cold-warm' gas, presuming that the opcode using it
|
||||||
|
// is also using 'warm' as constant factor.
|
||||||
|
// This method is used by:
|
||||||
|
// - extcodehash,
|
||||||
|
// - extcodesize,
|
||||||
|
// - (ext) balance
|
||||||
|
func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
addr := common.Address(stack.peek().Bytes20())
|
||||||
|
// Check slot presence in the access list
|
||||||
|
if !evm.StateDB.AddressInAccessList(addr) {
|
||||||
|
// If the caller cannot afford the cost, this change will be rolled back
|
||||||
|
evm.StateDB.AddAddressToAccessList(addr)
|
||||||
|
// The warm storage read cost is already charged as constantGas
|
||||||
|
return ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929, nil
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
|
||||||
|
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
addr := common.Address(stack.Back(1).Bytes20())
|
||||||
|
// Check slot presence in the access list
|
||||||
|
if !evm.StateDB.AddressInAccessList(addr) {
|
||||||
|
evm.StateDB.AddAddressToAccessList(addr)
|
||||||
|
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost
|
||||||
|
if !contract.UseGas(ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929) {
|
||||||
|
return 0, ErrOutOfGas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now call the old calculator, which takes into account
|
||||||
|
// - create new account
|
||||||
|
// - transfer value
|
||||||
|
// - memory expansion
|
||||||
|
// - 63/64ths rule
|
||||||
|
return oldCalculator(evm, contract, stack, mem, memorySize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall)
|
||||||
|
gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall)
|
||||||
|
gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall)
|
||||||
|
gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode)
|
||||||
|
)
|
||||||
|
|
||||||
|
func gasSelfdestructEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
var (
|
||||||
|
gas uint64
|
||||||
|
address = common.Address(stack.peek().Bytes20())
|
||||||
|
)
|
||||||
|
if !evm.StateDB.AddressInAccessList(address) {
|
||||||
|
// If the caller cannot afford the cost, this change will be rolled back
|
||||||
|
evm.StateDB.AddAddressToAccessList(address)
|
||||||
|
gas = ColdAccountAccessCostEIP2929
|
||||||
|
}
|
||||||
|
// if empty and transfers value
|
||||||
|
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
|
||||||
|
gas += params.CreateBySelfdestructGas
|
||||||
|
}
|
||||||
|
if !evm.StateDB.HasSuicided(contract.Address()) {
|
||||||
|
evm.StateDB.AddRefund(params.SelfdestructRefundGas)
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -65,7 +65,7 @@ func setDefaults(cfg *Config) {
|
||||||
PetersburgBlock: new(big.Int),
|
PetersburgBlock: new(big.Int),
|
||||||
IstanbulBlock: new(big.Int),
|
IstanbulBlock: new(big.Int),
|
||||||
MuirGlacierBlock: new(big.Int),
|
MuirGlacierBlock: new(big.Int),
|
||||||
YoloV1Block: nil,
|
YoloV2Block: nil,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,6 +113,14 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
|
||||||
vmenv = NewEnv(cfg)
|
vmenv = NewEnv(cfg)
|
||||||
sender = vm.AccountRef(cfg.Origin)
|
sender = vm.AccountRef(cfg.Origin)
|
||||||
)
|
)
|
||||||
|
if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) {
|
||||||
|
cfg.State.AddAddressToAccessList(cfg.Origin)
|
||||||
|
cfg.State.AddAddressToAccessList(address)
|
||||||
|
for _, addr := range vmenv.ActivePrecompiles() {
|
||||||
|
cfg.State.AddAddressToAccessList(addr)
|
||||||
|
cfg.State.AddAddressToAccessList(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
cfg.State.CreateAccount(address)
|
cfg.State.CreateAccount(address)
|
||||||
// set the receiver's (the executing contract) code for execution.
|
// set the receiver's (the executing contract) code for execution.
|
||||||
cfg.State.SetCode(address, code)
|
cfg.State.SetCode(address, code)
|
||||||
|
|
@ -142,6 +150,12 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
|
||||||
vmenv = NewEnv(cfg)
|
vmenv = NewEnv(cfg)
|
||||||
sender = vm.AccountRef(cfg.Origin)
|
sender = vm.AccountRef(cfg.Origin)
|
||||||
)
|
)
|
||||||
|
if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) {
|
||||||
|
cfg.State.AddAddressToAccessList(cfg.Origin)
|
||||||
|
for _, addr := range vmenv.ActivePrecompiles() {
|
||||||
|
cfg.State.AddAddressToAccessList(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Call the code with the given configuration.
|
// Call the code with the given configuration.
|
||||||
code, address, leftOverGas, err := vmenv.Create(
|
code, address, leftOverGas, err := vmenv.Create(
|
||||||
|
|
@ -164,6 +178,14 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
|
||||||
vmenv := NewEnv(cfg)
|
vmenv := NewEnv(cfg)
|
||||||
|
|
||||||
sender := cfg.State.GetOrNewStateObject(cfg.Origin)
|
sender := cfg.State.GetOrNewStateObject(cfg.Origin)
|
||||||
|
if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) {
|
||||||
|
cfg.State.AddAddressToAccessList(cfg.Origin)
|
||||||
|
cfg.State.AddAddressToAccessList(address)
|
||||||
|
for _, addr := range vmenv.ActivePrecompiles() {
|
||||||
|
cfg.State.AddAddressToAccessList(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Call the code with the given configuration.
|
// Call the code with the given configuration.
|
||||||
ret, leftOverGas, err := vmenv.Call(
|
ret, leftOverGas, err := vmenv.Call(
|
||||||
sender,
|
sender,
|
||||||
|
|
|
||||||
|
|
@ -722,3 +722,115 @@ func BenchmarkSimpleLoop(b *testing.B) {
|
||||||
//benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b)
|
//benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b)
|
||||||
//benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b)
|
//benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestEip2929Cases contains various testcases that are used for
|
||||||
|
// EIP-2929 about gas repricings
|
||||||
|
func TestEip2929Cases(t *testing.T) {
|
||||||
|
|
||||||
|
id := 1
|
||||||
|
prettyPrint := func(comment string, code []byte) {
|
||||||
|
|
||||||
|
instrs := make([]string, 0)
|
||||||
|
it := asm.NewInstructionIterator(code)
|
||||||
|
for it.Next() {
|
||||||
|
if it.Arg() != nil && 0 < len(it.Arg()) {
|
||||||
|
instrs = append(instrs, fmt.Sprintf("%v 0x%x", it.Op(), it.Arg()))
|
||||||
|
} else {
|
||||||
|
instrs = append(instrs, fmt.Sprintf("%v", it.Op()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ops := strings.Join(instrs, ", ")
|
||||||
|
fmt.Printf("### Case %d\n\n", id)
|
||||||
|
id++
|
||||||
|
fmt.Printf("%v\n\nBytecode: \n```\n0x%x\n```\nOperations: \n```\n%v\n```\n\n",
|
||||||
|
comment,
|
||||||
|
code, ops)
|
||||||
|
Execute(code, nil, &Config{
|
||||||
|
EVMConfig: vm.Config{
|
||||||
|
Debug: true,
|
||||||
|
Tracer: vm.NewMarkdownLogger(nil, os.Stdout),
|
||||||
|
ExtraEips: []int{2929},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // First eip testcase
|
||||||
|
code := []byte{
|
||||||
|
// Three checks against a precompile
|
||||||
|
byte(vm.PUSH1), 1, byte(vm.EXTCODEHASH), byte(vm.POP),
|
||||||
|
byte(vm.PUSH1), 2, byte(vm.EXTCODESIZE), byte(vm.POP),
|
||||||
|
byte(vm.PUSH1), 3, byte(vm.BALANCE), byte(vm.POP),
|
||||||
|
// Three checks against a non-precompile
|
||||||
|
byte(vm.PUSH1), 0xf1, byte(vm.EXTCODEHASH), byte(vm.POP),
|
||||||
|
byte(vm.PUSH1), 0xf2, byte(vm.EXTCODESIZE), byte(vm.POP),
|
||||||
|
byte(vm.PUSH1), 0xf3, byte(vm.BALANCE), byte(vm.POP),
|
||||||
|
// Same three checks (should be cheaper)
|
||||||
|
byte(vm.PUSH1), 0xf2, byte(vm.EXTCODEHASH), byte(vm.POP),
|
||||||
|
byte(vm.PUSH1), 0xf3, byte(vm.EXTCODESIZE), byte(vm.POP),
|
||||||
|
byte(vm.PUSH1), 0xf1, byte(vm.BALANCE), byte(vm.POP),
|
||||||
|
// Check the origin, and the 'this'
|
||||||
|
byte(vm.ORIGIN), byte(vm.BALANCE), byte(vm.POP),
|
||||||
|
byte(vm.ADDRESS), byte(vm.BALANCE), byte(vm.POP),
|
||||||
|
|
||||||
|
byte(vm.STOP),
|
||||||
|
}
|
||||||
|
prettyPrint("This checks `EXT`(codehash,codesize,balance) of precompiles, which should be `100`, "+
|
||||||
|
"and later checks the same operations twice against some non-precompiles. "+
|
||||||
|
"Those are cheaper second time they are accessed. Lastly, it checks the `BALANCE` of `origin` and `this`.", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // EXTCODECOPY
|
||||||
|
code := []byte{
|
||||||
|
// extcodecopy( 0xff,0,0,0,0)
|
||||||
|
byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset
|
||||||
|
byte(vm.PUSH1), 0xff, byte(vm.EXTCODECOPY),
|
||||||
|
// extcodecopy( 0xff,0,0,0,0)
|
||||||
|
byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset
|
||||||
|
byte(vm.PUSH1), 0xff, byte(vm.EXTCODECOPY),
|
||||||
|
// extcodecopy( this,0,0,0,0)
|
||||||
|
byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset
|
||||||
|
byte(vm.ADDRESS), byte(vm.EXTCODECOPY),
|
||||||
|
|
||||||
|
byte(vm.STOP),
|
||||||
|
}
|
||||||
|
prettyPrint("This checks `extcodecopy( 0xff,0,0,0,0)` twice, (should be expensive first time), "+
|
||||||
|
"and then does `extcodecopy( this,0,0,0,0)`.", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // SLOAD + SSTORE
|
||||||
|
code := []byte{
|
||||||
|
|
||||||
|
// Add slot `0x1` to access list
|
||||||
|
byte(vm.PUSH1), 0x01, byte(vm.SLOAD), byte(vm.POP), // SLOAD( 0x1) (add to access list)
|
||||||
|
// Write to `0x1` which is already in access list
|
||||||
|
byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x01, byte(vm.SSTORE), // SSTORE( loc: 0x01, val: 0x11)
|
||||||
|
// Write to `0x2` which is not in access list
|
||||||
|
byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x02, byte(vm.SSTORE), // SSTORE( loc: 0x02, val: 0x11)
|
||||||
|
// Write again to `0x2`
|
||||||
|
byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x02, byte(vm.SSTORE), // SSTORE( loc: 0x02, val: 0x11)
|
||||||
|
// Read slot in access list (0x2)
|
||||||
|
byte(vm.PUSH1), 0x02, byte(vm.SLOAD), // SLOAD( 0x2)
|
||||||
|
// Read slot in access list (0x1)
|
||||||
|
byte(vm.PUSH1), 0x01, byte(vm.SLOAD), // SLOAD( 0x1)
|
||||||
|
}
|
||||||
|
prettyPrint("This checks `sload( 0x1)` followed by `sstore(loc: 0x01, val:0x11)`, then 'naked' sstore:"+
|
||||||
|
"`sstore(loc: 0x02, val:0x11)` twice, and `sload(0x2)`, `sload(0x1)`. ", code)
|
||||||
|
}
|
||||||
|
{ // Call variants
|
||||||
|
code := []byte{
|
||||||
|
// identity precompile
|
||||||
|
byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
|
||||||
|
byte(vm.PUSH1), 0x04, byte(vm.PUSH1), 0x0, byte(vm.CALL), byte(vm.POP),
|
||||||
|
|
||||||
|
// random account - call 1
|
||||||
|
byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
|
||||||
|
byte(vm.PUSH1), 0xff, byte(vm.PUSH1), 0x0, byte(vm.CALL), byte(vm.POP),
|
||||||
|
|
||||||
|
// random account - call 2
|
||||||
|
byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
|
||||||
|
byte(vm.PUSH1), 0xff, byte(vm.PUSH1), 0x0, byte(vm.STATICCALL), byte(vm.POP),
|
||||||
|
}
|
||||||
|
prettyPrint("This calls the `identity`-precompile (cheap), then calls an account (expensive) and `staticcall`s the same"+
|
||||||
|
"account (cheap)", code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -389,6 +389,8 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return state.IteratorDump{}, err
|
return state.IteratorDump{}, err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return state.IteratorDump{}, errors.New("either block number or block hash must be specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
if maxResults > AccountRangeMaxResults || maxResults <= 0 {
|
if maxResults > AccountRangeMaxResults || maxResults <= 0 {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
|
@ -561,9 +562,28 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
|
||||||
|
|
||||||
// Execute transaction, either tracing all or just the requested one
|
// Execute transaction, either tracing all or just the requested one
|
||||||
var (
|
var (
|
||||||
signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number())
|
signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number())
|
||||||
dumps []string
|
dumps []string
|
||||||
|
chainConfig = api.eth.blockchain.Config()
|
||||||
|
canon = true
|
||||||
)
|
)
|
||||||
|
// Check if there are any overrides: the caller may wish to enable a future
|
||||||
|
// fork when executing this block. Note, such overrides are only applicable to the
|
||||||
|
// actual specified block, not any preceding blocks that we have to go through
|
||||||
|
// in order to obtain the state.
|
||||||
|
// Therefore, it's perfectly valid to specify `"futureForkBlock": 0`, to enable `futureFork`
|
||||||
|
|
||||||
|
if config != nil && config.Overrides != nil {
|
||||||
|
// Copy the config, to not screw up the main config
|
||||||
|
// Note: the Clique-part is _not_ deep copied
|
||||||
|
chainConfigCopy := new(params.ChainConfig)
|
||||||
|
*chainConfigCopy = *chainConfig
|
||||||
|
chainConfig = chainConfigCopy
|
||||||
|
if yolov2 := config.Overrides.YoloV2Block; yolov2 != nil {
|
||||||
|
chainConfig.YoloV2Block = yolov2
|
||||||
|
canon = false
|
||||||
|
}
|
||||||
|
}
|
||||||
for i, tx := range block.Transactions() {
|
for i, tx := range block.Transactions() {
|
||||||
// Prepare the trasaction for un-traced execution
|
// Prepare the trasaction for un-traced execution
|
||||||
var (
|
var (
|
||||||
|
|
@ -579,7 +599,9 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
|
||||||
if tx.Hash() == txHash || txHash == (common.Hash{}) {
|
if tx.Hash() == txHash || txHash == (common.Hash{}) {
|
||||||
// Generate a unique temporary file to dump it into
|
// Generate a unique temporary file to dump it into
|
||||||
prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4])
|
prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4])
|
||||||
|
if !canon {
|
||||||
|
prefix = fmt.Sprintf("%valt-", prefix)
|
||||||
|
}
|
||||||
dump, err = ioutil.TempFile(os.TempDir(), prefix)
|
dump, err = ioutil.TempFile(os.TempDir(), prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -595,7 +617,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Execute the transaction and flush any traces to disk
|
// Execute the transaction and flush any traces to disk
|
||||||
vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vmConf)
|
vmenv := vm.NewEVM(vmctx, statedb, chainConfig, vmConf)
|
||||||
_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
|
_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
|
||||||
if writer != nil {
|
if writer != nil {
|
||||||
writer.Flush()
|
writer.Flush()
|
||||||
|
|
|
||||||
|
|
@ -184,24 +184,35 @@ func (a *TarballArchive) Close() error {
|
||||||
return a.file.Close()
|
return a.file.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtractTarballArchive(archive string, dest string) error {
|
// ExtractArchive unpacks a .zip or .tar.gz archive to the destination directory.
|
||||||
// We're only interested in gzipped archives, wrap the reader now
|
func ExtractArchive(archive string, dest string) error {
|
||||||
ar, err := os.Open(archive)
|
ar, err := os.Open(archive)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer ar.Close()
|
defer ar.Close()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(archive, ".tar.gz"):
|
||||||
|
return extractTarball(ar, dest)
|
||||||
|
case strings.HasSuffix(archive, ".zip"):
|
||||||
|
return extractZip(ar, dest)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unhandled archive type %s", archive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractTarball unpacks a .tar.gz file.
|
||||||
|
func extractTarball(ar io.Reader, dest string) error {
|
||||||
gzr, err := gzip.NewReader(ar)
|
gzr, err := gzip.NewReader(ar)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer gzr.Close()
|
defer gzr.Close()
|
||||||
|
|
||||||
// Iterate over all the files in the tarball
|
|
||||||
tr := tar.NewReader(gzr)
|
tr := tar.NewReader(gzr)
|
||||||
for {
|
for {
|
||||||
// Fetch the next tarball header and abort if needed
|
// Move to the next file header.
|
||||||
header, err := tr.Next()
|
header, err := tr.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
|
|
@ -209,22 +220,69 @@ func ExtractTarballArchive(archive string, dest string) error {
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Figure out the target and create it
|
// We only care about regular files, directory modes
|
||||||
target := filepath.Join(dest, header.Name)
|
// and special file types are not supported.
|
||||||
|
if header.Typeflag == tar.TypeReg {
|
||||||
switch header.Typeflag {
|
armode := header.FileInfo().Mode()
|
||||||
case tar.TypeReg:
|
err := extractFile(header.Name, armode, tr, dest)
|
||||||
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
file, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("extract %s: %v", header.Name, err)
|
||||||
}
|
}
|
||||||
if _, err := io.Copy(file, tr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
file.Close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extractZip unpacks the given .zip file.
|
||||||
|
func extractZip(ar *os.File, dest string) error {
|
||||||
|
info, err := ar.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
zr, err := zip.NewReader(ar, info.Size())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, zf := range zr.File {
|
||||||
|
if !zf.Mode().IsRegular() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := zf.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = extractFile(zf.Name, zf.Mode(), data, dest)
|
||||||
|
data.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("extract %s: %v", zf.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractFile extracts a single file from an archive.
|
||||||
|
func extractFile(arpath string, armode os.FileMode, data io.Reader, dest string) error {
|
||||||
|
// Check that path is inside destination directory.
|
||||||
|
target := filepath.Join(dest, filepath.FromSlash(arpath))
|
||||||
|
if !strings.HasPrefix(target, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||||
|
return fmt.Errorf("path %q escapes archive destination", target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the destination directory exists.
|
||||||
|
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy file data.
|
||||||
|
file, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, armode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(file, data); err != nil {
|
||||||
|
file.Close()
|
||||||
|
os.Remove(target)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return file.Close()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
|
@ -152,3 +154,28 @@ func UploadSFTP(identityFile, host, dir string, files []string) error {
|
||||||
stdin.Close()
|
stdin.Close()
|
||||||
return sftp.Wait()
|
return sftp.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindMainPackages finds all 'main' packages in the given directory and returns their
|
||||||
|
// package paths.
|
||||||
|
func FindMainPackages(dir string) []string {
|
||||||
|
var commands []string
|
||||||
|
cmds, err := ioutil.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
pkgdir := filepath.Join(dir, cmd.Name())
|
||||||
|
pkgs, err := parser.ParseDir(token.NewFileSet(), pkgdir, nil, parser.PackageClauseOnly)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for name := range pkgs {
|
||||||
|
if name == "main" {
|
||||||
|
path := "./" + filepath.ToSlash(pkgdir)
|
||||||
|
commands = append(commands, path)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commands
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -599,7 +599,7 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre
|
||||||
if storageError != nil {
|
if storageError != nil {
|
||||||
return nil, storageError
|
return nil, storageError
|
||||||
}
|
}
|
||||||
storageProof[i] = StorageResult{key, (*hexutil.Big)(state.GetState(address, common.HexToHash(key)).Big()), common.ToHexArray(proof)}
|
storageProof[i] = StorageResult{key, (*hexutil.Big)(state.GetState(address, common.HexToHash(key)).Big()), toHexSlice(proof)}
|
||||||
} else {
|
} else {
|
||||||
storageProof[i] = StorageResult{key, &hexutil.Big{}, []string{}}
|
storageProof[i] = StorageResult{key, &hexutil.Big{}, []string{}}
|
||||||
}
|
}
|
||||||
|
|
@ -613,7 +613,7 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre
|
||||||
|
|
||||||
return &AccountResult{
|
return &AccountResult{
|
||||||
Address: address,
|
Address: address,
|
||||||
AccountProof: common.ToHexArray(accountProof),
|
AccountProof: toHexSlice(accountProof),
|
||||||
Balance: (*hexutil.Big)(state.GetBalance(address)),
|
Balance: (*hexutil.Big)(state.GetBalance(address)),
|
||||||
CodeHash: codeHash,
|
CodeHash: codeHash,
|
||||||
Nonce: hexutil.Uint64(state.GetNonce(address)),
|
Nonce: hexutil.Uint64(state.GetNonce(address)),
|
||||||
|
|
@ -1943,3 +1943,12 @@ func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toHexSlice creates a slice of hex-strings based on []byte.
|
||||||
|
func toHexSlice(b [][]byte) []string {
|
||||||
|
r := make([]string, len(b))
|
||||||
|
for i := range b {
|
||||||
|
r[i] = hexutil.Encode(b[i])
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -63,32 +64,165 @@ func MatchTests(tests []Test, expr string) []Test {
|
||||||
// RunTests executes all given tests in order and returns their results.
|
// RunTests executes all given tests in order and returns their results.
|
||||||
// If the report writer is non-nil, a test report is written to it in real time.
|
// If the report writer is non-nil, a test report is written to it in real time.
|
||||||
func RunTests(tests []Test, report io.Writer) []Result {
|
func RunTests(tests []Test, report io.Writer) []Result {
|
||||||
results := make([]Result, len(tests))
|
if report == nil {
|
||||||
|
report = ioutil.Discard
|
||||||
|
}
|
||||||
|
results := run(tests, newConsoleOutput(report))
|
||||||
|
fails := CountFailures(results)
|
||||||
|
fmt.Fprintf(report, "%v/%v tests passed.\n", len(tests)-fails, len(tests))
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunTAP runs the given tests and writes Test Anything Protocol output
|
||||||
|
// to the report writer.
|
||||||
|
func RunTAP(tests []Test, report io.Writer) []Result {
|
||||||
|
return run(tests, newTAP(report, len(tests)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(tests []Test, output testOutput) []Result {
|
||||||
|
var results = make([]Result, len(tests))
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
var output io.Writer
|
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
output = buffer
|
logOutput := io.MultiWriter(buffer, output)
|
||||||
if report != nil {
|
|
||||||
output = io.MultiWriter(buffer, report)
|
output.testStart(test.Name)
|
||||||
}
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
results[i].Name = test.Name
|
results[i].Name = test.Name
|
||||||
results[i].Failed = run(test, output)
|
results[i].Failed = runTest(test, logOutput)
|
||||||
results[i].Duration = time.Since(start)
|
results[i].Duration = time.Since(start)
|
||||||
results[i].Output = buffer.String()
|
results[i].Output = buffer.String()
|
||||||
if report != nil {
|
output.testResult(results[i])
|
||||||
printResult(results[i], report)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
func printResult(r Result, w io.Writer) {
|
// testOutput is implemented by output formats.
|
||||||
|
type testOutput interface {
|
||||||
|
testStart(name string)
|
||||||
|
Write([]byte) (int, error)
|
||||||
|
testResult(Result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// consoleOutput prints test results similarly to go test.
|
||||||
|
type consoleOutput struct {
|
||||||
|
out io.Writer
|
||||||
|
indented *indentWriter
|
||||||
|
curTest string
|
||||||
|
wroteHeader bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConsoleOutput(w io.Writer) *consoleOutput {
|
||||||
|
return &consoleOutput{
|
||||||
|
out: w,
|
||||||
|
indented: newIndentWriter(" ", w),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testStart signals the start of a new test.
|
||||||
|
func (c *consoleOutput) testStart(name string) {
|
||||||
|
c.curTest = name
|
||||||
|
c.wroteHeader = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write handles test log output.
|
||||||
|
func (c *consoleOutput) Write(b []byte) (int, error) {
|
||||||
|
if !c.wroteHeader {
|
||||||
|
// This is the first output line from the test. Print a "-- RUN" header.
|
||||||
|
fmt.Fprintln(c.out, "-- RUN", c.curTest)
|
||||||
|
c.wroteHeader = true
|
||||||
|
}
|
||||||
|
return c.indented.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// testResult prints the final test result line.
|
||||||
|
func (c *consoleOutput) testResult(r Result) {
|
||||||
|
c.indented.flush()
|
||||||
pd := r.Duration.Truncate(100 * time.Microsecond)
|
pd := r.Duration.Truncate(100 * time.Microsecond)
|
||||||
if r.Failed {
|
if r.Failed {
|
||||||
fmt.Fprintf(w, "-- FAIL %s (%v)\n", r.Name, pd)
|
fmt.Fprintf(c.out, "-- FAIL %s (%v)\n", r.Name, pd)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(w, "-- OK %s (%v)\n", r.Name, pd)
|
fmt.Fprintf(c.out, "-- OK %s (%v)\n", r.Name, pd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tapOutput produces Test Anything Protocol v13 output.
|
||||||
|
type tapOutput struct {
|
||||||
|
out io.Writer
|
||||||
|
indented *indentWriter
|
||||||
|
counter int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTAP(out io.Writer, numTests int) *tapOutput {
|
||||||
|
fmt.Fprintf(out, "1..%d\n", numTests)
|
||||||
|
return &tapOutput{
|
||||||
|
out: out,
|
||||||
|
indented: newIndentWriter("# ", out),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tapOutput) testStart(name string) {
|
||||||
|
t.counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write does nothing for TAP because there is no real-time output of test logs.
|
||||||
|
func (t *tapOutput) Write(b []byte) (int, error) {
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tapOutput) testResult(r Result) {
|
||||||
|
status := "ok"
|
||||||
|
if r.Failed {
|
||||||
|
status = "not ok"
|
||||||
|
}
|
||||||
|
fmt.Fprintln(t.out, status, t.counter, r.Name)
|
||||||
|
t.indented.Write([]byte(r.Output))
|
||||||
|
t.indented.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// indentWriter indents all written text.
|
||||||
|
type indentWriter struct {
|
||||||
|
out io.Writer
|
||||||
|
indent string
|
||||||
|
inLine bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIndentWriter(indent string, out io.Writer) *indentWriter {
|
||||||
|
return &indentWriter{out: out, indent: indent}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *indentWriter) Write(b []byte) (n int, err error) {
|
||||||
|
for len(b) > 0 {
|
||||||
|
if !w.inLine {
|
||||||
|
if _, err = io.WriteString(w.out, w.indent); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
w.inLine = true
|
||||||
|
}
|
||||||
|
|
||||||
|
end := bytes.IndexByte(b, '\n')
|
||||||
|
if end == -1 {
|
||||||
|
nn, err := w.out.Write(b)
|
||||||
|
n += nn
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
line := b[:end+1]
|
||||||
|
nn, err := w.out.Write(line)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
b = b[end+1:]
|
||||||
|
w.inLine = false
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// flush ensures the current line is terminated.
|
||||||
|
func (w *indentWriter) flush() {
|
||||||
|
if w.inLine {
|
||||||
|
fmt.Println(w.out)
|
||||||
|
w.inLine = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,11 +240,11 @@ func CountFailures(rr []Result) int {
|
||||||
// Run executes a single test.
|
// Run executes a single test.
|
||||||
func Run(test Test) (bool, string) {
|
func Run(test Test) (bool, string) {
|
||||||
output := new(bytes.Buffer)
|
output := new(bytes.Buffer)
|
||||||
failed := run(test, output)
|
failed := runTest(test, output)
|
||||||
return failed, output.String()
|
return failed, output.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(test Test, output io.Writer) bool {
|
func runTest(test Test, output io.Writer) bool {
|
||||||
t := &T{output: output}
|
t := &T{output: output}
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -137,6 +271,9 @@ type T struct {
|
||||||
output io.Writer
|
output io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper exists for compatibility with testing.T.
|
||||||
|
func (t *T) Helper() {}
|
||||||
|
|
||||||
// FailNow marks the test as having failed and stops its execution by calling
|
// FailNow marks the test as having failed and stops its execution by calling
|
||||||
// runtime.Goexit (which then runs all deferred calls in the current goroutine).
|
// runtime.Goexit (which then runs all deferred calls in the current goroutine).
|
||||||
func (t *T) FailNow() {
|
func (t *T) FailNow() {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
package utesting
|
package utesting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
@ -53,3 +55,85 @@ func TestTest(t *testing.T) {
|
||||||
t.Fatalf("wrong result for panicking test: %#v", results[2])
|
t.Fatalf("wrong result for panicking test: %#v", results[2])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var outputTests = []Test{
|
||||||
|
{
|
||||||
|
Name: "TestWithLogs",
|
||||||
|
Fn: func(t *T) {
|
||||||
|
t.Log("output line 1")
|
||||||
|
t.Log("output line 2\noutput line 3")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "TestNoLogs",
|
||||||
|
Fn: func(t *T) {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "FailWithLogs",
|
||||||
|
Fn: func(t *T) {
|
||||||
|
t.Log("output line 1")
|
||||||
|
t.Error("failed 1")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "FailMessage",
|
||||||
|
Fn: func(t *T) {
|
||||||
|
t.Error("failed 2")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "FailNoOutput",
|
||||||
|
Fn: func(t *T) {
|
||||||
|
t.Fail()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutput(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
RunTests(outputTests, &buf)
|
||||||
|
|
||||||
|
want := regexp.MustCompile(`
|
||||||
|
^-- RUN TestWithLogs
|
||||||
|
output line 1
|
||||||
|
output line 2
|
||||||
|
output line 3
|
||||||
|
-- OK TestWithLogs \([^)]+\)
|
||||||
|
-- OK TestNoLogs \([^)]+\)
|
||||||
|
-- RUN FailWithLogs
|
||||||
|
output line 1
|
||||||
|
failed 1
|
||||||
|
-- FAIL FailWithLogs \([^)]+\)
|
||||||
|
-- RUN FailMessage
|
||||||
|
failed 2
|
||||||
|
-- FAIL FailMessage \([^)]+\)
|
||||||
|
-- FAIL FailNoOutput \([^)]+\)
|
||||||
|
2/5 tests passed.
|
||||||
|
$`[1:])
|
||||||
|
if !want.MatchString(buf.String()) {
|
||||||
|
t.Fatalf("output does not match: %q", buf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputTAP(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
RunTAP(outputTests, &buf)
|
||||||
|
|
||||||
|
want := `
|
||||||
|
1..5
|
||||||
|
ok 1 TestWithLogs
|
||||||
|
# output line 1
|
||||||
|
# output line 2
|
||||||
|
# output line 3
|
||||||
|
ok 2 TestNoLogs
|
||||||
|
not ok 3 FailWithLogs
|
||||||
|
# output line 1
|
||||||
|
# failed 1
|
||||||
|
not ok 4 FailMessage
|
||||||
|
# failed 2
|
||||||
|
not ok 5 FailNoOutput
|
||||||
|
`
|
||||||
|
if buf.String() != want[1:] {
|
||||||
|
t.Fatalf("output does not match: %q", buf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,13 +102,7 @@ func (h *clientHandler) handle(p *serverPeer) error {
|
||||||
p.Log().Debug("Light Ethereum peer connected", "name", p.Name())
|
p.Log().Debug("Light Ethereum peer connected", "name", p.Name())
|
||||||
|
|
||||||
// Execute the LES handshake
|
// Execute the LES handshake
|
||||||
var (
|
if err := p.Handshake(h.backend.blockchain.Genesis().Hash()); err != nil {
|
||||||
head = h.backend.blockchain.CurrentHeader()
|
|
||||||
hash = head.Hash()
|
|
||||||
number = head.Number.Uint64()
|
|
||||||
td = h.backend.blockchain.GetTd(hash, number)
|
|
||||||
)
|
|
||||||
if err := p.Handshake(td, hash, number, h.backend.blockchain.Genesis().Hash(), nil); err != nil {
|
|
||||||
p.Log().Debug("Light Ethereum handshake failed", "err", err)
|
p.Log().Debug("Light Ethereum handshake failed", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -46,19 +45,6 @@ const (
|
||||||
inactiveTimeout = time.Second * 10
|
inactiveTimeout = time.Second * 10
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
clientPoolSetup = &nodestate.Setup{}
|
|
||||||
clientField = clientPoolSetup.NewField("clientInfo", reflect.TypeOf(&clientInfo{}))
|
|
||||||
connAddressField = clientPoolSetup.NewField("connAddr", reflect.TypeOf(""))
|
|
||||||
balanceTrackerSetup = lps.NewBalanceTrackerSetup(clientPoolSetup)
|
|
||||||
priorityPoolSetup = lps.NewPriorityPoolSetup(clientPoolSetup)
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
balanceTrackerSetup.Connect(connAddressField, priorityPoolSetup.CapacityField)
|
|
||||||
priorityPoolSetup.Connect(balanceTrackerSetup.BalanceField, balanceTrackerSetup.UpdateFlag) // NodeBalance implements nodePriority
|
|
||||||
}
|
|
||||||
|
|
||||||
// clientPool implements a client database that assigns a priority to each client
|
// clientPool implements a client database that assigns a priority to each client
|
||||||
// based on a positive and negative balance. Positive balance is externally assigned
|
// based on a positive and negative balance. Positive balance is externally assigned
|
||||||
// to prioritized clients and is decreased with connection time and processed
|
// to prioritized clients and is decreased with connection time and processed
|
||||||
|
|
@ -119,8 +105,7 @@ type clientInfo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newClientPool creates a new client pool
|
// newClientPool creates a new client pool
|
||||||
func newClientPool(lespayDb ethdb.Database, minCap uint64, connectedBias time.Duration, clock mclock.Clock, removePeer func(enode.ID)) *clientPool {
|
func newClientPool(ns *nodestate.NodeStateMachine, lespayDb ethdb.Database, minCap uint64, connectedBias time.Duration, clock mclock.Clock, removePeer func(enode.ID)) *clientPool {
|
||||||
ns := nodestate.NewNodeStateMachine(nil, nil, clock, clientPoolSetup)
|
|
||||||
pool := &clientPool{
|
pool := &clientPool{
|
||||||
ns: ns,
|
ns: ns,
|
||||||
BalanceTrackerSetup: balanceTrackerSetup,
|
BalanceTrackerSetup: balanceTrackerSetup,
|
||||||
|
|
@ -147,7 +132,7 @@ func newClientPool(lespayDb ethdb.Database, minCap uint64, connectedBias time.Du
|
||||||
})
|
})
|
||||||
|
|
||||||
ns.SubscribeState(pool.ActiveFlag.Or(pool.PriorityFlag), func(node *enode.Node, oldState, newState nodestate.Flags) {
|
ns.SubscribeState(pool.ActiveFlag.Or(pool.PriorityFlag), func(node *enode.Node, oldState, newState nodestate.Flags) {
|
||||||
c, _ := ns.GetField(node, clientField).(*clientInfo)
|
c, _ := ns.GetField(node, clientInfoField).(*clientInfo)
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +157,7 @@ func newClientPool(lespayDb ethdb.Database, minCap uint64, connectedBias time.Du
|
||||||
if oldState.Equals(pool.ActiveFlag) && newState.Equals(pool.InactiveFlag) {
|
if oldState.Equals(pool.ActiveFlag) && newState.Equals(pool.InactiveFlag) {
|
||||||
clientDeactivatedMeter.Mark(1)
|
clientDeactivatedMeter.Mark(1)
|
||||||
log.Debug("Client deactivated", "id", node.ID())
|
log.Debug("Client deactivated", "id", node.ID())
|
||||||
c, _ := ns.GetField(node, clientField).(*clientInfo)
|
c, _ := ns.GetField(node, clientInfoField).(*clientInfo)
|
||||||
if c == nil || !c.peer.allowInactive() {
|
if c == nil || !c.peer.allowInactive() {
|
||||||
pool.removePeer(node.ID())
|
pool.removePeer(node.ID())
|
||||||
}
|
}
|
||||||
|
|
@ -190,13 +175,11 @@ func newClientPool(lespayDb ethdb.Database, minCap uint64, connectedBias time.Du
|
||||||
newCap, _ := newValue.(uint64)
|
newCap, _ := newValue.(uint64)
|
||||||
totalConnected += newCap - oldCap
|
totalConnected += newCap - oldCap
|
||||||
totalConnectedGauge.Update(int64(totalConnected))
|
totalConnectedGauge.Update(int64(totalConnected))
|
||||||
c, _ := ns.GetField(node, clientField).(*clientInfo)
|
c, _ := ns.GetField(node, clientInfoField).(*clientInfo)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
c.peer.updateCapacity(newCap)
|
c.peer.updateCapacity(newCap)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ns.Start()
|
|
||||||
return pool
|
return pool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,7 +193,6 @@ func (f *clientPool) stop() {
|
||||||
f.disconnectNode(node)
|
f.disconnectNode(node)
|
||||||
})
|
})
|
||||||
f.bt.Stop()
|
f.bt.Stop()
|
||||||
f.ns.Stop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// connect should be called after a successful handshake. If the connection was
|
// connect should be called after a successful handshake. If the connection was
|
||||||
|
|
@ -225,7 +207,7 @@ func (f *clientPool) connect(peer clientPoolPeer) (uint64, error) {
|
||||||
}
|
}
|
||||||
// Dedup connected peers.
|
// Dedup connected peers.
|
||||||
node, freeID := peer.Node(), peer.freeClientId()
|
node, freeID := peer.Node(), peer.freeClientId()
|
||||||
if f.ns.GetField(node, clientField) != nil {
|
if f.ns.GetField(node, clientInfoField) != nil {
|
||||||
log.Debug("Client already connected", "address", freeID, "id", node.ID().String())
|
log.Debug("Client already connected", "address", freeID, "id", node.ID().String())
|
||||||
return 0, fmt.Errorf("Client already connected address=%s id=%s", freeID, node.ID().String())
|
return 0, fmt.Errorf("Client already connected address=%s id=%s", freeID, node.ID().String())
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +219,7 @@ func (f *clientPool) connect(peer clientPoolPeer) (uint64, error) {
|
||||||
connected: true,
|
connected: true,
|
||||||
connectedAt: now,
|
connectedAt: now,
|
||||||
}
|
}
|
||||||
f.ns.SetField(node, clientField, c)
|
f.ns.SetField(node, clientInfoField, c)
|
||||||
f.ns.SetField(node, connAddressField, freeID)
|
f.ns.SetField(node, connAddressField, freeID)
|
||||||
if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance == nil {
|
if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance == nil {
|
||||||
f.disconnect(peer)
|
f.disconnect(peer)
|
||||||
|
|
@ -280,7 +262,7 @@ func (f *clientPool) disconnect(p clientPoolPeer) {
|
||||||
// disconnectNode removes node fields and flags related to connected status
|
// disconnectNode removes node fields and flags related to connected status
|
||||||
func (f *clientPool) disconnectNode(node *enode.Node) {
|
func (f *clientPool) disconnectNode(node *enode.Node) {
|
||||||
f.ns.SetField(node, connAddressField, nil)
|
f.ns.SetField(node, connAddressField, nil)
|
||||||
f.ns.SetField(node, clientField, nil)
|
f.ns.SetField(node, clientInfoField, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setDefaultFactors sets the default price factors applied to subsequently connected clients
|
// setDefaultFactors sets the default price factors applied to subsequently connected clients
|
||||||
|
|
@ -299,7 +281,8 @@ func (f *clientPool) capacityInfo() (uint64, uint64, uint64) {
|
||||||
defer f.lock.Unlock()
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
// total priority active cap will be supported when the token issuer module is added
|
// total priority active cap will be supported when the token issuer module is added
|
||||||
return f.capLimit, f.pp.ActiveCapacity(), 0
|
_, activeCap := f.pp.Active()
|
||||||
|
return f.capLimit, activeCap, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// setLimits sets the maximum number and total capacity of connected clients,
|
// setLimits sets the maximum number and total capacity of connected clients,
|
||||||
|
|
@ -314,13 +297,13 @@ func (f *clientPool) setLimits(totalConn int, totalCap uint64) {
|
||||||
|
|
||||||
// setCapacity sets the assigned capacity of a connected client
|
// setCapacity sets the assigned capacity of a connected client
|
||||||
func (f *clientPool) setCapacity(node *enode.Node, freeID string, capacity uint64, bias time.Duration, setCap bool) (uint64, error) {
|
func (f *clientPool) setCapacity(node *enode.Node, freeID string, capacity uint64, bias time.Duration, setCap bool) (uint64, error) {
|
||||||
c, _ := f.ns.GetField(node, clientField).(*clientInfo)
|
c, _ := f.ns.GetField(node, clientInfoField).(*clientInfo)
|
||||||
if c == nil {
|
if c == nil {
|
||||||
if setCap {
|
if setCap {
|
||||||
return 0, fmt.Errorf("client %064x is not connected", node.ID())
|
return 0, fmt.Errorf("client %064x is not connected", node.ID())
|
||||||
}
|
}
|
||||||
c = &clientInfo{node: node}
|
c = &clientInfo{node: node}
|
||||||
f.ns.SetField(node, clientField, c)
|
f.ns.SetField(node, clientInfoField, c)
|
||||||
f.ns.SetField(node, connAddressField, freeID)
|
f.ns.SetField(node, connAddressField, freeID)
|
||||||
if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance == nil {
|
if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance == nil {
|
||||||
log.Error("BalanceField is missing", "node", node.ID())
|
log.Error("BalanceField is missing", "node", node.ID())
|
||||||
|
|
@ -328,7 +311,7 @@ func (f *clientPool) setCapacity(node *enode.Node, freeID string, capacity uint6
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
f.ns.SetField(node, connAddressField, nil)
|
f.ns.SetField(node, connAddressField, nil)
|
||||||
f.ns.SetField(node, clientField, nil)
|
f.ns.SetField(node, clientInfoField, nil)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
|
|
@ -370,7 +353,7 @@ func (f *clientPool) forClients(ids []enode.ID, cb func(client *clientInfo)) {
|
||||||
|
|
||||||
if len(ids) == 0 {
|
if len(ids) == 0 {
|
||||||
f.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) {
|
f.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) {
|
||||||
c, _ := f.ns.GetField(node, clientField).(*clientInfo)
|
c, _ := f.ns.GetField(node, clientInfoField).(*clientInfo)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
cb(c)
|
cb(c)
|
||||||
}
|
}
|
||||||
|
|
@ -381,12 +364,12 @@ func (f *clientPool) forClients(ids []enode.ID, cb func(client *clientInfo)) {
|
||||||
if node == nil {
|
if node == nil {
|
||||||
node = enode.SignNull(&enr.Record{}, id)
|
node = enode.SignNull(&enr.Record{}, id)
|
||||||
}
|
}
|
||||||
c, _ := f.ns.GetField(node, clientField).(*clientInfo)
|
c, _ := f.ns.GetField(node, clientInfoField).(*clientInfo)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
cb(c)
|
cb(c)
|
||||||
} else {
|
} else {
|
||||||
c = &clientInfo{node: node}
|
c = &clientInfo{node: node}
|
||||||
f.ns.SetField(node, clientField, c)
|
f.ns.SetField(node, clientInfoField, c)
|
||||||
f.ns.SetField(node, connAddressField, "")
|
f.ns.SetField(node, connAddressField, "")
|
||||||
if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance != nil {
|
if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance != nil {
|
||||||
cb(c)
|
cb(c)
|
||||||
|
|
@ -394,7 +377,7 @@ func (f *clientPool) forClients(ids []enode.ID, cb func(client *clientInfo)) {
|
||||||
log.Error("BalanceField is missing")
|
log.Error("BalanceField is missing")
|
||||||
}
|
}
|
||||||
f.ns.SetField(node, connAddressField, nil)
|
f.ns.SetField(node, connAddressField, nil)
|
||||||
f.ns.SetField(node, clientField, nil)
|
f.ns.SetField(node, clientInfoField, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,11 @@ type poolTestPeer struct {
|
||||||
inactiveAllowed bool
|
inactiveAllowed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testStateMachine() *nodestate.NodeStateMachine {
|
||||||
|
return nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func newPoolTestPeer(i int, disconnCh chan int) *poolTestPeer {
|
func newPoolTestPeer(i int, disconnCh chan int) *poolTestPeer {
|
||||||
return &poolTestPeer{
|
return &poolTestPeer{
|
||||||
index: i,
|
index: i,
|
||||||
|
|
@ -91,7 +96,7 @@ func (i *poolTestPeer) allowInactive() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBalance(pool *clientPool, p *poolTestPeer) (pos, neg uint64) {
|
func getBalance(pool *clientPool, p *poolTestPeer) (pos, neg uint64) {
|
||||||
temp := pool.ns.GetField(p.node, clientField) == nil
|
temp := pool.ns.GetField(p.node, clientInfoField) == nil
|
||||||
if temp {
|
if temp {
|
||||||
pool.ns.SetField(p.node, connAddressField, p.freeClientId())
|
pool.ns.SetField(p.node, connAddressField, p.freeClientId())
|
||||||
}
|
}
|
||||||
|
|
@ -128,8 +133,9 @@ func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, rando
|
||||||
disconnFn = func(id enode.ID) {
|
disconnFn = func(id enode.ID) {
|
||||||
disconnCh <- int(id[0]) + int(id[1])<<8
|
disconnCh <- int(id[0]) + int(id[1])<<8
|
||||||
}
|
}
|
||||||
pool = newClientPool(db, 1, 0, &clock, disconnFn)
|
pool = newClientPool(testStateMachine(), db, 1, 0, &clock, disconnFn)
|
||||||
)
|
)
|
||||||
|
pool.ns.Start()
|
||||||
|
|
||||||
pool.setLimits(activeLimit, uint64(activeLimit))
|
pool.setLimits(activeLimit, uint64(activeLimit))
|
||||||
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
||||||
|
|
@ -233,7 +239,8 @@ func TestConnectPaidClient(t *testing.T) {
|
||||||
clock mclock.Simulated
|
clock mclock.Simulated
|
||||||
db = rawdb.NewMemoryDatabase()
|
db = rawdb.NewMemoryDatabase()
|
||||||
)
|
)
|
||||||
pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {})
|
pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {})
|
||||||
|
pool.ns.Start()
|
||||||
defer pool.stop()
|
defer pool.stop()
|
||||||
pool.setLimits(10, uint64(10))
|
pool.setLimits(10, uint64(10))
|
||||||
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
||||||
|
|
@ -248,7 +255,8 @@ func TestConnectPaidClientToSmallPool(t *testing.T) {
|
||||||
clock mclock.Simulated
|
clock mclock.Simulated
|
||||||
db = rawdb.NewMemoryDatabase()
|
db = rawdb.NewMemoryDatabase()
|
||||||
)
|
)
|
||||||
pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {})
|
pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {})
|
||||||
|
pool.ns.Start()
|
||||||
defer pool.stop()
|
defer pool.stop()
|
||||||
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
||||||
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
||||||
|
|
@ -266,7 +274,8 @@ func TestConnectPaidClientToFullPool(t *testing.T) {
|
||||||
db = rawdb.NewMemoryDatabase()
|
db = rawdb.NewMemoryDatabase()
|
||||||
)
|
)
|
||||||
removeFn := func(enode.ID) {} // Noop
|
removeFn := func(enode.ID) {} // Noop
|
||||||
pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn)
|
pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn)
|
||||||
|
pool.ns.Start()
|
||||||
defer pool.stop()
|
defer pool.stop()
|
||||||
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
||||||
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
||||||
|
|
@ -295,7 +304,8 @@ func TestPaidClientKickedOut(t *testing.T) {
|
||||||
removeFn := func(id enode.ID) {
|
removeFn := func(id enode.ID) {
|
||||||
kickedCh <- int(id[0])
|
kickedCh <- int(id[0])
|
||||||
}
|
}
|
||||||
pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn)
|
pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn)
|
||||||
|
pool.ns.Start()
|
||||||
pool.bt.SetExpirationTCs(0, 0)
|
pool.bt.SetExpirationTCs(0, 0)
|
||||||
defer pool.stop()
|
defer pool.stop()
|
||||||
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
||||||
|
|
@ -325,7 +335,8 @@ func TestConnectFreeClient(t *testing.T) {
|
||||||
clock mclock.Simulated
|
clock mclock.Simulated
|
||||||
db = rawdb.NewMemoryDatabase()
|
db = rawdb.NewMemoryDatabase()
|
||||||
)
|
)
|
||||||
pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {})
|
pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {})
|
||||||
|
pool.ns.Start()
|
||||||
defer pool.stop()
|
defer pool.stop()
|
||||||
pool.setLimits(10, uint64(10))
|
pool.setLimits(10, uint64(10))
|
||||||
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
||||||
|
|
@ -341,7 +352,8 @@ func TestConnectFreeClientToFullPool(t *testing.T) {
|
||||||
db = rawdb.NewMemoryDatabase()
|
db = rawdb.NewMemoryDatabase()
|
||||||
)
|
)
|
||||||
removeFn := func(enode.ID) {} // Noop
|
removeFn := func(enode.ID) {} // Noop
|
||||||
pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn)
|
pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn)
|
||||||
|
pool.ns.Start()
|
||||||
defer pool.stop()
|
defer pool.stop()
|
||||||
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
||||||
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
||||||
|
|
@ -370,7 +382,8 @@ func TestFreeClientKickedOut(t *testing.T) {
|
||||||
kicked = make(chan int, 100)
|
kicked = make(chan int, 100)
|
||||||
)
|
)
|
||||||
removeFn := func(id enode.ID) { kicked <- int(id[0]) }
|
removeFn := func(id enode.ID) { kicked <- int(id[0]) }
|
||||||
pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn)
|
pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn)
|
||||||
|
pool.ns.Start()
|
||||||
defer pool.stop()
|
defer pool.stop()
|
||||||
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
||||||
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
||||||
|
|
@ -411,7 +424,8 @@ func TestPositiveBalanceCalculation(t *testing.T) {
|
||||||
kicked = make(chan int, 10)
|
kicked = make(chan int, 10)
|
||||||
)
|
)
|
||||||
removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop
|
removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop
|
||||||
pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn)
|
pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn)
|
||||||
|
pool.ns.Start()
|
||||||
defer pool.stop()
|
defer pool.stop()
|
||||||
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
||||||
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
||||||
|
|
@ -434,7 +448,8 @@ func TestDowngradePriorityClient(t *testing.T) {
|
||||||
kicked = make(chan int, 10)
|
kicked = make(chan int, 10)
|
||||||
)
|
)
|
||||||
removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop
|
removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop
|
||||||
pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn)
|
pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn)
|
||||||
|
pool.ns.Start()
|
||||||
defer pool.stop()
|
defer pool.stop()
|
||||||
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
||||||
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
|
||||||
|
|
@ -468,7 +483,8 @@ func TestNegativeBalanceCalculation(t *testing.T) {
|
||||||
clock mclock.Simulated
|
clock mclock.Simulated
|
||||||
db = rawdb.NewMemoryDatabase()
|
db = rawdb.NewMemoryDatabase()
|
||||||
)
|
)
|
||||||
pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {})
|
pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {})
|
||||||
|
pool.ns.Start()
|
||||||
defer pool.stop()
|
defer pool.stop()
|
||||||
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
pool.setLimits(10, uint64(10)) // Total capacity limit is 10
|
||||||
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1})
|
pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1})
|
||||||
|
|
@ -503,7 +519,8 @@ func TestInactiveClient(t *testing.T) {
|
||||||
clock mclock.Simulated
|
clock mclock.Simulated
|
||||||
db = rawdb.NewMemoryDatabase()
|
db = rawdb.NewMemoryDatabase()
|
||||||
)
|
)
|
||||||
pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {})
|
pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {})
|
||||||
|
pool.ns.Start()
|
||||||
defer pool.stop()
|
defer pool.stop()
|
||||||
pool.setLimits(2, uint64(2))
|
pool.setLimits(2, uint64(2))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ func (e lesEntry) ENRKey() string {
|
||||||
|
|
||||||
// setupDiscovery creates the node discovery source for the eth protocol.
|
// setupDiscovery creates the node discovery source for the eth protocol.
|
||||||
func (eth *LightEthereum) setupDiscovery(cfg *p2p.Config) (enode.Iterator, error) {
|
func (eth *LightEthereum) setupDiscovery(cfg *p2p.Config) (enode.Iterator, error) {
|
||||||
if /*cfg.NoDiscovery || */ len(eth.config.DiscoveryURLs) == 0 {
|
if cfg.NoDiscovery || len(eth.config.DiscoveryURLs) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
client := dnsdisc.NewClient(dnsdisc.Config{})
|
client := dnsdisc.NewClient(dnsdisc.Config{})
|
||||||
|
|
|
||||||
|
|
@ -253,12 +253,12 @@ func (pp *PriorityPool) SetActiveBias(bias time.Duration) {
|
||||||
pp.tryActivate()
|
pp.tryActivate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActiveCapacity returns the total capacity of currently active nodes
|
// Active returns the number and total capacity of currently active nodes
|
||||||
func (pp *PriorityPool) ActiveCapacity() uint64 {
|
func (pp *PriorityPool) Active() (uint64, uint64) {
|
||||||
pp.lock.Lock()
|
pp.lock.Lock()
|
||||||
defer pp.lock.Unlock()
|
defer pp.lock.Unlock()
|
||||||
|
|
||||||
return pp.activeCap
|
return pp.activeCount, pp.activeCap
|
||||||
}
|
}
|
||||||
|
|
||||||
// inactiveSetIndex callback updates ppNodeInfo item index in inactiveQueue
|
// inactiveSetIndex callback updates ppNodeInfo item index in inactiveQueue
|
||||||
|
|
|
||||||
184
les/peer.go
184
les/peer.go
|
|
@ -126,7 +126,7 @@ type peerCommons struct {
|
||||||
frozen uint32 // Flag whether the peer is frozen.
|
frozen uint32 // Flag whether the peer is frozen.
|
||||||
announceType uint64 // New block announcement type.
|
announceType uint64 // New block announcement type.
|
||||||
serving uint32 // The status indicates the peer is served.
|
serving uint32 // The status indicates the peer is served.
|
||||||
headInfo blockInfo // Latest block information.
|
headInfo blockInfo // Last announced block information.
|
||||||
|
|
||||||
// Background task queue for caching peer tasks and executing in order.
|
// Background task queue for caching peer tasks and executing in order.
|
||||||
sendQueue *utils.ExecQueue
|
sendQueue *utils.ExecQueue
|
||||||
|
|
@ -255,6 +255,8 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g
|
||||||
// Add some basic handshake fields
|
// Add some basic handshake fields
|
||||||
send = send.add("protocolVersion", uint64(p.version))
|
send = send.add("protocolVersion", uint64(p.version))
|
||||||
send = send.add("networkId", p.network)
|
send = send.add("networkId", p.network)
|
||||||
|
// Note: the head info announced at handshake is only used in case of server peers
|
||||||
|
// but dummy values are still announced by clients for compatibility with older servers
|
||||||
send = send.add("headTd", td)
|
send = send.add("headTd", td)
|
||||||
send = send.add("headHash", head)
|
send = send.add("headHash", head)
|
||||||
send = send.add("headNum", headNum)
|
send = send.add("headNum", headNum)
|
||||||
|
|
@ -273,24 +275,14 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g
|
||||||
if size > allowedUpdateBytes {
|
if size > allowedUpdateBytes {
|
||||||
return errResp(ErrRequestRejected, "")
|
return errResp(ErrRequestRejected, "")
|
||||||
}
|
}
|
||||||
var rGenesis, rHash common.Hash
|
var rGenesis common.Hash
|
||||||
var rVersion, rNetwork, rNum uint64
|
var rVersion, rNetwork uint64
|
||||||
var rTd *big.Int
|
|
||||||
if err := recv.get("protocolVersion", &rVersion); err != nil {
|
if err := recv.get("protocolVersion", &rVersion); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := recv.get("networkId", &rNetwork); err != nil {
|
if err := recv.get("networkId", &rNetwork); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := recv.get("headTd", &rTd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := recv.get("headHash", &rHash); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := recv.get("headNum", &rNum); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := recv.get("genesisHash", &rGenesis); err != nil {
|
if err := recv.get("genesisHash", &rGenesis); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -303,7 +295,6 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g
|
||||||
if int(rVersion) != p.version {
|
if int(rVersion) != p.version {
|
||||||
return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", rVersion, p.version)
|
return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", rVersion, p.version)
|
||||||
}
|
}
|
||||||
p.headInfo = blockInfo{Hash: rHash, Number: rNum, Td: rTd}
|
|
||||||
if recvCallback != nil {
|
if recvCallback != nil {
|
||||||
return recvCallback(recv)
|
return recvCallback(recv)
|
||||||
}
|
}
|
||||||
|
|
@ -569,9 +560,11 @@ func (p *serverPeer) updateHead(hash common.Hash, number uint64, td *big.Int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handshake executes the les protocol handshake, negotiating version number,
|
// Handshake executes the les protocol handshake, negotiating version number,
|
||||||
// network IDs, difficulties, head and genesis blocks.
|
// network IDs and genesis blocks.
|
||||||
func (p *serverPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, server *LesServer) error {
|
func (p *serverPeer) Handshake(genesis common.Hash) error {
|
||||||
return p.handshake(td, head, headNum, genesis, func(lists *keyValueList) {
|
// Note: there is no need to share local head with a server but older servers still
|
||||||
|
// require these fields so we announce zero values.
|
||||||
|
return p.handshake(common.Big0, common.Hash{}, 0, genesis, func(lists *keyValueList) {
|
||||||
// Add some client-specific handshake fields
|
// Add some client-specific handshake fields
|
||||||
//
|
//
|
||||||
// Enable signed announcement randomly even the server is not trusted.
|
// Enable signed announcement randomly even the server is not trusted.
|
||||||
|
|
@ -581,6 +574,21 @@ func (p *serverPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge
|
||||||
}
|
}
|
||||||
*lists = (*lists).add("announceType", p.announceType)
|
*lists = (*lists).add("announceType", p.announceType)
|
||||||
}, func(recv keyValueMap) error {
|
}, func(recv keyValueMap) error {
|
||||||
|
var (
|
||||||
|
rHash common.Hash
|
||||||
|
rNum uint64
|
||||||
|
rTd *big.Int
|
||||||
|
)
|
||||||
|
if err := recv.get("headTd", &rTd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := recv.get("headHash", &rHash); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := recv.get("headNum", &rNum); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.headInfo = blockInfo{Hash: rHash, Number: rNum, Td: rTd}
|
||||||
if recv.get("serveChainSince", &p.chainSince) != nil {
|
if recv.get("serveChainSince", &p.chainSince) != nil {
|
||||||
p.onlyAnnounce = true
|
p.onlyAnnounce = true
|
||||||
}
|
}
|
||||||
|
|
@ -937,6 +945,9 @@ func (p *clientPeer) freezeClient() {
|
||||||
// Handshake executes the les protocol handshake, negotiating version number,
|
// Handshake executes the les protocol handshake, negotiating version number,
|
||||||
// network IDs, difficulties, head and genesis blocks.
|
// network IDs, difficulties, head and genesis blocks.
|
||||||
func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, server *LesServer) error {
|
func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, server *LesServer) error {
|
||||||
|
// Note: clientPeer.headInfo should contain the last head announced to the client by us.
|
||||||
|
// The values announced in the handshake are dummy values for compatibility reasons and should be ignored.
|
||||||
|
p.headInfo = blockInfo{Hash: head, Number: headNum, Td: td}
|
||||||
return p.handshake(td, head, headNum, genesis, func(lists *keyValueList) {
|
return p.handshake(td, head, headNum, genesis, func(lists *keyValueList) {
|
||||||
// Add some information which services server can offer.
|
// Add some information which services server can offer.
|
||||||
if !server.config.UltraLightOnlyAnnounce {
|
if !server.config.UltraLightOnlyAnnounce {
|
||||||
|
|
@ -1009,145 +1020,6 @@ type serverPeerSubscriber interface {
|
||||||
unregisterPeer(*serverPeer)
|
unregisterPeer(*serverPeer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// clientPeerSubscriber is an interface to notify services about added or
|
|
||||||
// removed client peers
|
|
||||||
type clientPeerSubscriber interface {
|
|
||||||
registerPeer(*clientPeer)
|
|
||||||
unregisterPeer(*clientPeer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// clientPeerSet represents the set of active client peers currently
|
|
||||||
// participating in the Light Ethereum sub-protocol.
|
|
||||||
type clientPeerSet struct {
|
|
||||||
peers map[string]*clientPeer
|
|
||||||
// subscribers is a batch of subscribers and peerset will notify
|
|
||||||
// these subscribers when the peerset changes(new client peer is
|
|
||||||
// added or removed)
|
|
||||||
subscribers []clientPeerSubscriber
|
|
||||||
closed bool
|
|
||||||
lock sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// newClientPeerSet creates a new peer set to track the client peers.
|
|
||||||
func newClientPeerSet() *clientPeerSet {
|
|
||||||
return &clientPeerSet{peers: make(map[string]*clientPeer)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// subscribe adds a service to be notified about added or removed
|
|
||||||
// peers and also register all active peers into the given service.
|
|
||||||
func (ps *clientPeerSet) subscribe(sub clientPeerSubscriber) {
|
|
||||||
ps.lock.Lock()
|
|
||||||
defer ps.lock.Unlock()
|
|
||||||
|
|
||||||
ps.subscribers = append(ps.subscribers, sub)
|
|
||||||
for _, p := range ps.peers {
|
|
||||||
sub.registerPeer(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unSubscribe removes the specified service from the subscriber pool.
|
|
||||||
func (ps *clientPeerSet) unSubscribe(sub clientPeerSubscriber) {
|
|
||||||
ps.lock.Lock()
|
|
||||||
defer ps.lock.Unlock()
|
|
||||||
|
|
||||||
for i, s := range ps.subscribers {
|
|
||||||
if s == sub {
|
|
||||||
ps.subscribers = append(ps.subscribers[:i], ps.subscribers[i+1:]...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// register adds a new peer into the peer set, or returns an error if the
|
|
||||||
// peer is already known.
|
|
||||||
func (ps *clientPeerSet) register(peer *clientPeer) error {
|
|
||||||
ps.lock.Lock()
|
|
||||||
defer ps.lock.Unlock()
|
|
||||||
|
|
||||||
if ps.closed {
|
|
||||||
return errClosed
|
|
||||||
}
|
|
||||||
if _, exist := ps.peers[peer.id]; exist {
|
|
||||||
return errAlreadyRegistered
|
|
||||||
}
|
|
||||||
ps.peers[peer.id] = peer
|
|
||||||
for _, sub := range ps.subscribers {
|
|
||||||
sub.registerPeer(peer)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// unregister removes a remote peer from the peer set, disabling any further
|
|
||||||
// actions to/from that particular entity. It also initiates disconnection
|
|
||||||
// at the networking layer.
|
|
||||||
func (ps *clientPeerSet) unregister(id string) error {
|
|
||||||
ps.lock.Lock()
|
|
||||||
defer ps.lock.Unlock()
|
|
||||||
|
|
||||||
p, ok := ps.peers[id]
|
|
||||||
if !ok {
|
|
||||||
return errNotRegistered
|
|
||||||
}
|
|
||||||
delete(ps.peers, id)
|
|
||||||
for _, sub := range ps.subscribers {
|
|
||||||
sub.unregisterPeer(p)
|
|
||||||
}
|
|
||||||
p.Peer.Disconnect(p2p.DiscRequested)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ids returns a list of all registered peer IDs
|
|
||||||
func (ps *clientPeerSet) ids() []string {
|
|
||||||
ps.lock.RLock()
|
|
||||||
defer ps.lock.RUnlock()
|
|
||||||
|
|
||||||
var ids []string
|
|
||||||
for id := range ps.peers {
|
|
||||||
ids = append(ids, id)
|
|
||||||
}
|
|
||||||
return ids
|
|
||||||
}
|
|
||||||
|
|
||||||
// peer retrieves the registered peer with the given id.
|
|
||||||
func (ps *clientPeerSet) peer(id string) *clientPeer {
|
|
||||||
ps.lock.RLock()
|
|
||||||
defer ps.lock.RUnlock()
|
|
||||||
|
|
||||||
return ps.peers[id]
|
|
||||||
}
|
|
||||||
|
|
||||||
// len returns if the current number of peers in the set.
|
|
||||||
func (ps *clientPeerSet) len() int {
|
|
||||||
ps.lock.RLock()
|
|
||||||
defer ps.lock.RUnlock()
|
|
||||||
|
|
||||||
return len(ps.peers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// allClientPeers returns all client peers in a list.
|
|
||||||
func (ps *clientPeerSet) allPeers() []*clientPeer {
|
|
||||||
ps.lock.RLock()
|
|
||||||
defer ps.lock.RUnlock()
|
|
||||||
|
|
||||||
list := make([]*clientPeer, 0, len(ps.peers))
|
|
||||||
for _, p := range ps.peers {
|
|
||||||
list = append(list, p)
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
// close disconnects all peers. No new peers can be registered
|
|
||||||
// after close has returned.
|
|
||||||
func (ps *clientPeerSet) close() {
|
|
||||||
ps.lock.Lock()
|
|
||||||
defer ps.lock.Unlock()
|
|
||||||
|
|
||||||
for _, p := range ps.peers {
|
|
||||||
p.Disconnect(p2p.DiscQuitting)
|
|
||||||
}
|
|
||||||
ps.closed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverPeerSet represents the set of active server peers currently
|
// serverPeerSet represents the set of active server peers currently
|
||||||
// participating in the Light Ethereum sub-protocol.
|
// participating in the Light Ethereum sub-protocol.
|
||||||
type serverPeerSet struct {
|
type serverPeerSet struct {
|
||||||
|
|
|
||||||
|
|
@ -174,12 +174,6 @@ var errorToString = map[int]string{
|
||||||
ErrMissingKey: "Key missing from list",
|
ErrMissingKey: "Key missing from list",
|
||||||
}
|
}
|
||||||
|
|
||||||
type announceBlock struct {
|
|
||||||
Hash common.Hash // Hash of one particular block being announced
|
|
||||||
Number uint64 // Number of one particular block being announced
|
|
||||||
Td *big.Int // Total difficulty of one particular block being announced
|
|
||||||
}
|
|
||||||
|
|
||||||
// announceData is the network packet for the block announcements.
|
// announceData is the network packet for the block announcements.
|
||||||
type announceData struct {
|
type announceData struct {
|
||||||
Hash common.Hash // Hash of one particular block being announced
|
Hash common.Hash // Hash of one particular block being announced
|
||||||
|
|
@ -199,7 +193,7 @@ func (a *announceData) sanityCheck() error {
|
||||||
|
|
||||||
// sign adds a signature to the block announcement by the given privKey
|
// sign adds a signature to the block announcement by the given privKey
|
||||||
func (a *announceData) sign(privKey *ecdsa.PrivateKey) {
|
func (a *announceData) sign(privKey *ecdsa.PrivateKey) {
|
||||||
rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td})
|
rlp, _ := rlp.EncodeToBytes(blockInfo{a.Hash, a.Number, a.Td})
|
||||||
sig, _ := crypto.Sign(crypto.Keccak256(rlp), privKey)
|
sig, _ := crypto.Sign(crypto.Keccak256(rlp), privKey)
|
||||||
a.Update = a.Update.add("sign", sig)
|
a.Update = a.Update.add("sign", sig)
|
||||||
}
|
}
|
||||||
|
|
@ -210,7 +204,7 @@ func (a *announceData) checkSignature(id enode.ID, update keyValueMap) error {
|
||||||
if err := update.get("sign", &sig); err != nil {
|
if err := update.get("sign", &sig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td})
|
rlp, _ := rlp.EncodeToBytes(blockInfo{a.Hash, a.Number, a.Td})
|
||||||
recPubkey, err := crypto.SigToPub(crypto.Keccak256(rlp), sig)
|
recPubkey, err := crypto.SigToPub(crypto.Keccak256(rlp), sig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
|
@ -31,17 +32,32 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/nodestate"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
serverSetup = &nodestate.Setup{}
|
||||||
|
clientPeerField = serverSetup.NewField("clientPeer", reflect.TypeOf(&clientPeer{}))
|
||||||
|
clientInfoField = serverSetup.NewField("clientInfo", reflect.TypeOf(&clientInfo{}))
|
||||||
|
connAddressField = serverSetup.NewField("connAddr", reflect.TypeOf(""))
|
||||||
|
balanceTrackerSetup = lps.NewBalanceTrackerSetup(serverSetup)
|
||||||
|
priorityPoolSetup = lps.NewPriorityPoolSetup(serverSetup)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
balanceTrackerSetup.Connect(connAddressField, priorityPoolSetup.CapacityField)
|
||||||
|
priorityPoolSetup.Connect(balanceTrackerSetup.BalanceField, balanceTrackerSetup.UpdateFlag) // NodeBalance implements nodePriority
|
||||||
|
}
|
||||||
|
|
||||||
type LesServer struct {
|
type LesServer struct {
|
||||||
lesCommons
|
lesCommons
|
||||||
|
|
||||||
|
ns *nodestate.NodeStateMachine
|
||||||
archiveMode bool // Flag whether the ethereum node runs in archive mode.
|
archiveMode bool // Flag whether the ethereum node runs in archive mode.
|
||||||
peers *clientPeerSet
|
|
||||||
serverset *serverSet
|
|
||||||
handler *serverHandler
|
handler *serverHandler
|
||||||
|
broadcaster *broadcaster
|
||||||
lesTopics []discv5.Topic
|
lesTopics []discv5.Topic
|
||||||
privateKey *ecdsa.PrivateKey
|
privateKey *ecdsa.PrivateKey
|
||||||
|
|
||||||
|
|
@ -60,6 +76,7 @@ type LesServer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
||||||
|
ns := nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup)
|
||||||
// Collect les protocol version information supported by local node.
|
// Collect les protocol version information supported by local node.
|
||||||
lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions))
|
lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions))
|
||||||
for i, pv := range AdvertiseProtocolVersions {
|
for i, pv := range AdvertiseProtocolVersions {
|
||||||
|
|
@ -83,9 +100,9 @@ func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesSer
|
||||||
bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency, true),
|
bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency, true),
|
||||||
closeCh: make(chan struct{}),
|
closeCh: make(chan struct{}),
|
||||||
},
|
},
|
||||||
|
ns: ns,
|
||||||
archiveMode: e.ArchiveMode(),
|
archiveMode: e.ArchiveMode(),
|
||||||
peers: newClientPeerSet(),
|
broadcaster: newBroadcaster(ns),
|
||||||
serverset: newServerSet(),
|
|
||||||
lesTopics: lesTopics,
|
lesTopics: lesTopics,
|
||||||
fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}),
|
fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}),
|
||||||
servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100),
|
servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100),
|
||||||
|
|
@ -116,7 +133,7 @@ func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesSer
|
||||||
srv.maxCapacity = totalRecharge
|
srv.maxCapacity = totalRecharge
|
||||||
}
|
}
|
||||||
srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2)
|
srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2)
|
||||||
srv.clientPool = newClientPool(srv.chainDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, func(id enode.ID) { go srv.peers.unregister(id.String()) })
|
srv.clientPool = newClientPool(ns, srv.chainDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, srv.dropClient)
|
||||||
srv.clientPool.setDefaultFactors(lps.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1})
|
srv.clientPool.setDefaultFactors(lps.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1})
|
||||||
|
|
||||||
checkpoint := srv.latestLocalCheckpoint()
|
checkpoint := srv.latestLocalCheckpoint()
|
||||||
|
|
@ -130,6 +147,13 @@ func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesSer
|
||||||
node.RegisterAPIs(srv.APIs())
|
node.RegisterAPIs(srv.APIs())
|
||||||
node.RegisterLifecycle(srv)
|
node.RegisterLifecycle(srv)
|
||||||
|
|
||||||
|
// disconnect all peers at nsm shutdown
|
||||||
|
ns.SubscribeField(clientPeerField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
|
||||||
|
if state.Equals(serverSetup.OfflineFlag()) && oldValue != nil {
|
||||||
|
oldValue.(*clientPeer).Peer.Disconnect(p2p.DiscRequested)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
ns.Start()
|
||||||
return srv, nil
|
return srv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,7 +182,7 @@ func (s *LesServer) APIs() []rpc.API {
|
||||||
|
|
||||||
func (s *LesServer) Protocols() []p2p.Protocol {
|
func (s *LesServer) Protocols() []p2p.Protocol {
|
||||||
ps := s.makeProtocols(ServerProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} {
|
ps := s.makeProtocols(ServerProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} {
|
||||||
if p := s.peers.peer(id.String()); p != nil {
|
if p := s.getClient(id); p != nil {
|
||||||
return p.Info()
|
return p.Info()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -173,6 +197,7 @@ func (s *LesServer) Protocols() []p2p.Protocol {
|
||||||
// Start starts the LES server
|
// Start starts the LES server
|
||||||
func (s *LesServer) Start() error {
|
func (s *LesServer) Start() error {
|
||||||
s.privateKey = s.p2pSrv.PrivateKey
|
s.privateKey = s.p2pSrv.PrivateKey
|
||||||
|
s.broadcaster.setSignerKey(s.privateKey)
|
||||||
s.handler.start()
|
s.handler.start()
|
||||||
|
|
||||||
s.wg.Add(1)
|
s.wg.Add(1)
|
||||||
|
|
@ -198,19 +223,11 @@ func (s *LesServer) Start() error {
|
||||||
func (s *LesServer) Stop() error {
|
func (s *LesServer) Stop() error {
|
||||||
close(s.closeCh)
|
close(s.closeCh)
|
||||||
|
|
||||||
// Disconnect existing connections with other LES servers.
|
s.clientPool.stop()
|
||||||
s.serverset.close()
|
s.ns.Stop()
|
||||||
|
|
||||||
// Disconnect existing sessions.
|
|
||||||
// This also closes the gate for any new registrations on the peer set.
|
|
||||||
// sessions which are already established but not added to pm.peers yet
|
|
||||||
// will exit when they try to register.
|
|
||||||
s.peers.close()
|
|
||||||
|
|
||||||
s.fcManager.Stop()
|
s.fcManager.Stop()
|
||||||
s.costTracker.stop()
|
s.costTracker.stop()
|
||||||
s.handler.stop()
|
s.handler.stop()
|
||||||
s.clientPool.stop() // client pool should be closed after handler.
|
|
||||||
s.servingQueue.stop()
|
s.servingQueue.stop()
|
||||||
|
|
||||||
// Note, bloom trie indexer is closed by parent bloombits indexer.
|
// Note, bloom trie indexer is closed by parent bloombits indexer.
|
||||||
|
|
@ -279,3 +296,18 @@ func (s *LesServer) capacityManagement() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LesServer) getClient(id enode.ID) *clientPeer {
|
||||||
|
if node := s.ns.GetNode(id); node != nil {
|
||||||
|
if p, ok := s.ns.GetField(node, clientPeerField).(*clientPeer); ok {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LesServer) dropClient(id enode.ID) {
|
||||||
|
if p := s.getClient(id); p != nil {
|
||||||
|
p.Peer.Disconnect(p2p.DiscRequested)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package les
|
package les
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
@ -36,6 +37,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/nodestate"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
@ -91,7 +94,7 @@ func newServerHandler(server *LesServer, blockchain *core.BlockChain, chainDb et
|
||||||
// start starts the server handler.
|
// start starts the server handler.
|
||||||
func (h *serverHandler) start() {
|
func (h *serverHandler) start() {
|
||||||
h.wg.Add(1)
|
h.wg.Add(1)
|
||||||
go h.broadcastHeaders()
|
go h.broadcastLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop stops the server handler.
|
// stop stops the server handler.
|
||||||
|
|
@ -123,47 +126,61 @@ func (h *serverHandler) handle(p *clientPeer) error {
|
||||||
p.Log().Debug("Light Ethereum handshake failed", "err", err)
|
p.Log().Debug("Light Ethereum handshake failed", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if p.server {
|
// Reject the duplicated peer, otherwise register it to peerset.
|
||||||
if err := h.server.serverset.register(p); err != nil {
|
var registered bool
|
||||||
return err
|
if err := h.server.ns.Operation(func() {
|
||||||
|
if h.server.ns.GetField(p.Node(), clientPeerField) != nil {
|
||||||
|
registered = true
|
||||||
|
} else {
|
||||||
|
h.server.ns.SetFieldSub(p.Node(), clientPeerField, p)
|
||||||
}
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if registered {
|
||||||
|
return errAlreadyRegistered
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
h.server.ns.SetField(p.Node(), clientPeerField, nil)
|
||||||
|
if p.fcClient != nil { // is nil when connecting another server
|
||||||
|
p.fcClient.Disconnect()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if p.server {
|
||||||
// connected to another server, no messages expected, just wait for disconnection
|
// connected to another server, no messages expected, just wait for disconnection
|
||||||
_, err := p.rw.ReadMsg()
|
_, err := p.rw.ReadMsg()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Reject light clients if server is not synced.
|
// Reject light clients if server is not synced.
|
||||||
|
//
|
||||||
|
// Put this checking here, so that "non-synced" les-server peers are still allowed
|
||||||
|
// to keep the connection.
|
||||||
if !h.synced() {
|
if !h.synced() {
|
||||||
p.Log().Debug("Light server not synced, rejecting peer")
|
p.Log().Debug("Light server not synced, rejecting peer")
|
||||||
return p2p.DiscRequested
|
return p2p.DiscRequested
|
||||||
}
|
}
|
||||||
defer p.fcClient.Disconnect()
|
|
||||||
|
|
||||||
// Disconnect the inbound peer if it's rejected by clientPool
|
// Disconnect the inbound peer if it's rejected by clientPool
|
||||||
if cap, err := h.server.clientPool.connect(p); cap != p.fcParams.MinRecharge || err != nil {
|
if cap, err := h.server.clientPool.connect(p); cap != p.fcParams.MinRecharge || err != nil {
|
||||||
p.Log().Debug("Light Ethereum peer rejected", "err", errFullClientPool)
|
p.Log().Debug("Light Ethereum peer rejected", "err", errFullClientPool)
|
||||||
return errFullClientPool
|
return errFullClientPool
|
||||||
}
|
}
|
||||||
p.balance, _ = h.server.clientPool.ns.GetField(p.Node(), h.server.clientPool.BalanceField).(*lps.NodeBalance)
|
p.balance, _ = h.server.ns.GetField(p.Node(), h.server.clientPool.BalanceField).(*lps.NodeBalance)
|
||||||
if p.balance == nil {
|
if p.balance == nil {
|
||||||
return p2p.DiscRequested
|
return p2p.DiscRequested
|
||||||
}
|
}
|
||||||
// Register the peer locally
|
activeCount, _ := h.server.clientPool.pp.Active()
|
||||||
if err := h.server.peers.register(p); err != nil {
|
clientConnectionGauge.Update(int64(activeCount))
|
||||||
h.server.clientPool.disconnect(p)
|
|
||||||
p.Log().Error("Light Ethereum peer registration failed", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
clientConnectionGauge.Update(int64(h.server.peers.len()))
|
|
||||||
|
|
||||||
var wg sync.WaitGroup // Wait group used to track all in-flight task routines.
|
var wg sync.WaitGroup // Wait group used to track all in-flight task routines.
|
||||||
|
|
||||||
connectedAt := mclock.Now()
|
connectedAt := mclock.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
wg.Wait() // Ensure all background task routines have exited.
|
wg.Wait() // Ensure all background task routines have exited.
|
||||||
h.server.peers.unregister(p.id)
|
|
||||||
h.server.clientPool.disconnect(p)
|
h.server.clientPool.disconnect(p)
|
||||||
p.balance = nil
|
p.balance = nil
|
||||||
clientConnectionGauge.Update(int64(h.server.peers.len()))
|
activeCount, _ := h.server.clientPool.pp.Active()
|
||||||
|
clientConnectionGauge.Update(int64(activeCount))
|
||||||
connectionTimer.Update(time.Duration(mclock.Now() - connectedAt))
|
connectionTimer.Update(time.Duration(mclock.Now() - connectedAt))
|
||||||
}()
|
}()
|
||||||
// Mark the peer starts to be served.
|
// Mark the peer starts to be served.
|
||||||
|
|
@ -334,7 +351,6 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error {
|
||||||
origin = h.blockchain.GetHeaderByNumber(query.Origin.Number)
|
origin = h.blockchain.GetHeaderByNumber(query.Origin.Number)
|
||||||
}
|
}
|
||||||
if origin == nil {
|
if origin == nil {
|
||||||
p.bumpInvalid()
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
headers = append(headers, origin)
|
headers = append(headers, origin)
|
||||||
|
|
@ -911,11 +927,11 @@ func (h *serverHandler) txStatus(hash common.Hash) light.TxStatus {
|
||||||
return stat
|
return stat
|
||||||
}
|
}
|
||||||
|
|
||||||
// broadcastHeaders broadcasts new block information to all connected light
|
// broadcastLoop broadcasts new block information to all connected light
|
||||||
// clients. According to the agreement between client and server, server should
|
// clients. According to the agreement between client and server, server should
|
||||||
// only broadcast new announcement if the total difficulty is higher than the
|
// only broadcast new announcement if the total difficulty is higher than the
|
||||||
// last one. Besides server will add the signature if client requires.
|
// last one. Besides server will add the signature if client requires.
|
||||||
func (h *serverHandler) broadcastHeaders() {
|
func (h *serverHandler) broadcastLoop() {
|
||||||
defer h.wg.Done()
|
defer h.wg.Done()
|
||||||
|
|
||||||
headCh := make(chan core.ChainHeadEvent, 10)
|
headCh := make(chan core.ChainHeadEvent, 10)
|
||||||
|
|
@ -929,10 +945,6 @@ func (h *serverHandler) broadcastHeaders() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case ev := <-headCh:
|
case ev := <-headCh:
|
||||||
peers := h.server.peers.allPeers()
|
|
||||||
if len(peers) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
header := ev.Block.Header()
|
header := ev.Block.Header()
|
||||||
hash, number := header.Hash(), header.Number.Uint64()
|
hash, number := header.Hash(), header.Number.Uint64()
|
||||||
td := h.blockchain.GetTd(hash, number)
|
td := h.blockchain.GetTd(hash, number)
|
||||||
|
|
@ -944,33 +956,79 @@ func (h *serverHandler) broadcastHeaders() {
|
||||||
reorg = lastHead.Number.Uint64() - rawdb.FindCommonAncestor(h.chainDb, header, lastHead).Number.Uint64()
|
reorg = lastHead.Number.Uint64() - rawdb.FindCommonAncestor(h.chainDb, header, lastHead).Number.Uint64()
|
||||||
}
|
}
|
||||||
lastHead, lastTd = header, td
|
lastHead, lastTd = header, td
|
||||||
|
|
||||||
log.Debug("Announcing block to peers", "number", number, "hash", hash, "td", td, "reorg", reorg)
|
log.Debug("Announcing block to peers", "number", number, "hash", hash, "td", td, "reorg", reorg)
|
||||||
var (
|
h.server.broadcaster.broadcast(announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg})
|
||||||
signed bool
|
|
||||||
signedAnnounce announceData
|
|
||||||
)
|
|
||||||
announce := announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg}
|
|
||||||
for _, p := range peers {
|
|
||||||
p := p
|
|
||||||
switch p.announceType {
|
|
||||||
case announceTypeSimple:
|
|
||||||
if !p.queueSend(func() { p.sendAnnounce(announce) }) {
|
|
||||||
log.Debug("Drop announcement because queue is full", "number", number, "hash", hash)
|
|
||||||
}
|
|
||||||
case announceTypeSigned:
|
|
||||||
if !signed {
|
|
||||||
signedAnnounce = announce
|
|
||||||
signedAnnounce.sign(h.server.privateKey)
|
|
||||||
signed = true
|
|
||||||
}
|
|
||||||
if !p.queueSend(func() { p.sendAnnounce(signedAnnounce) }) {
|
|
||||||
log.Debug("Drop announcement because queue is full", "number", number, "hash", hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case <-h.closeCh:
|
case <-h.closeCh:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// broadcaster sends new header announcements to active client peers
|
||||||
|
type broadcaster struct {
|
||||||
|
ns *nodestate.NodeStateMachine
|
||||||
|
privateKey *ecdsa.PrivateKey
|
||||||
|
lastAnnounce, signedAnnounce announceData
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBroadcaster creates a new broadcaster
|
||||||
|
func newBroadcaster(ns *nodestate.NodeStateMachine) *broadcaster {
|
||||||
|
b := &broadcaster{ns: ns}
|
||||||
|
ns.SubscribeState(priorityPoolSetup.ActiveFlag, func(node *enode.Node, oldState, newState nodestate.Flags) {
|
||||||
|
if newState.Equals(priorityPoolSetup.ActiveFlag) {
|
||||||
|
// send last announcement to activated peers
|
||||||
|
b.sendTo(node)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// setSignerKey sets the signer key for signed announcements. Should be called before
|
||||||
|
// starting the protocol handler.
|
||||||
|
func (b *broadcaster) setSignerKey(privateKey *ecdsa.PrivateKey) {
|
||||||
|
b.privateKey = privateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// broadcast sends the given announcements to all active peers
|
||||||
|
func (b *broadcaster) broadcast(announce announceData) {
|
||||||
|
b.ns.Operation(func() {
|
||||||
|
// iterate in an Operation to ensure that the active set does not change while iterating
|
||||||
|
b.lastAnnounce = announce
|
||||||
|
b.ns.ForEach(priorityPoolSetup.ActiveFlag, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) {
|
||||||
|
b.sendTo(node)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendTo sends the most recent announcement to the given node unless the same or higher Td
|
||||||
|
// announcement has already been sent.
|
||||||
|
func (b *broadcaster) sendTo(node *enode.Node) {
|
||||||
|
if b.lastAnnounce.Td == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p, _ := b.ns.GetField(node, clientPeerField).(*clientPeer); p != nil {
|
||||||
|
if p.headInfo.Td == nil || b.lastAnnounce.Td.Cmp(p.headInfo.Td) > 0 {
|
||||||
|
announce := b.lastAnnounce
|
||||||
|
switch p.announceType {
|
||||||
|
case announceTypeSimple:
|
||||||
|
if !p.queueSend(func() { p.sendAnnounce(announce) }) {
|
||||||
|
log.Debug("Drop announcement because queue is full", "number", announce.Number, "hash", announce.Hash)
|
||||||
|
} else {
|
||||||
|
log.Debug("Sent announcement", "number", announce.Number, "hash", announce.Hash)
|
||||||
|
}
|
||||||
|
case announceTypeSigned:
|
||||||
|
if b.signedAnnounce.Hash != b.lastAnnounce.Hash {
|
||||||
|
b.signedAnnounce = b.lastAnnounce
|
||||||
|
b.signedAnnounce.sign(b.privateKey)
|
||||||
|
}
|
||||||
|
announce := b.signedAnnounce
|
||||||
|
if !p.queueSend(func() { p.sendAnnounce(announce) }) {
|
||||||
|
log.Debug("Drop announcement because queue is full", "number", announce.Number, "hash", announce.Hash)
|
||||||
|
} else {
|
||||||
|
log.Debug("Sent announcement", "number", announce.Number, "hash", announce.Hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.headInfo = blockInfo{b.lastAnnounce.Hash, b.lastAnnounce.Number, b.lastAnnounce.Td}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/light"
|
"github.com/ethereum/go-ethereum/light"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/nodestate"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -227,7 +228,7 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index
|
||||||
return client.handler
|
return client.handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Database, peers *clientPeerSet, clock mclock.Clock) (*serverHandler, *backends.SimulatedBackend) {
|
func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Database, clock mclock.Clock) (*serverHandler, *backends.SimulatedBackend) {
|
||||||
var (
|
var (
|
||||||
gspec = core.Genesis{
|
gspec = core.Genesis{
|
||||||
Config: params.AllEthashProtocolChanges,
|
Config: params.AllEthashProtocolChanges,
|
||||||
|
|
@ -263,6 +264,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da
|
||||||
}
|
}
|
||||||
oracle = checkpointoracle.New(checkpointConfig, getLocal)
|
oracle = checkpointoracle.New(checkpointConfig, getLocal)
|
||||||
}
|
}
|
||||||
|
ns := nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup)
|
||||||
server := &LesServer{
|
server := &LesServer{
|
||||||
lesCommons: lesCommons{
|
lesCommons: lesCommons{
|
||||||
genesis: genesis.Hash(),
|
genesis: genesis.Hash(),
|
||||||
|
|
@ -274,7 +276,8 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da
|
||||||
oracle: oracle,
|
oracle: oracle,
|
||||||
closeCh: make(chan struct{}),
|
closeCh: make(chan struct{}),
|
||||||
},
|
},
|
||||||
peers: peers,
|
ns: ns,
|
||||||
|
broadcaster: newBroadcaster(ns),
|
||||||
servingQueue: newServingQueue(int64(time.Millisecond*10), 1),
|
servingQueue: newServingQueue(int64(time.Millisecond*10), 1),
|
||||||
defParams: flowcontrol.ServerParams{
|
defParams: flowcontrol.ServerParams{
|
||||||
BufLimit: testBufLimit,
|
BufLimit: testBufLimit,
|
||||||
|
|
@ -284,13 +287,14 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da
|
||||||
}
|
}
|
||||||
server.costTracker, server.minCapacity = newCostTracker(db, server.config)
|
server.costTracker, server.minCapacity = newCostTracker(db, server.config)
|
||||||
server.costTracker.testCostList = testCostList(0) // Disable flow control mechanism.
|
server.costTracker.testCostList = testCostList(0) // Disable flow control mechanism.
|
||||||
server.clientPool = newClientPool(db, testBufRecharge, defaultConnectedBias, clock, func(id enode.ID) {})
|
server.clientPool = newClientPool(ns, db, testBufRecharge, defaultConnectedBias, clock, func(id enode.ID) {})
|
||||||
server.clientPool.setLimits(10000, 10000) // Assign enough capacity for clientpool
|
server.clientPool.setLimits(10000, 10000) // Assign enough capacity for clientpool
|
||||||
server.handler = newServerHandler(server, simulation.Blockchain(), db, txpool, func() bool { return true })
|
server.handler = newServerHandler(server, simulation.Blockchain(), db, txpool, func() bool { return true })
|
||||||
if server.oracle != nil {
|
if server.oracle != nil {
|
||||||
server.oracle.Start(simulation)
|
server.oracle.Start(simulation)
|
||||||
}
|
}
|
||||||
server.servingQueue.setThreads(4)
|
server.servingQueue.setThreads(4)
|
||||||
|
ns.Start()
|
||||||
server.handler.start()
|
server.handler.start()
|
||||||
return server.handler, simulation
|
return server.handler, simulation
|
||||||
}
|
}
|
||||||
|
|
@ -463,7 +467,7 @@ func newServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallba
|
||||||
if simClock {
|
if simClock {
|
||||||
clock = &mclock.Simulated{}
|
clock = &mclock.Simulated{}
|
||||||
}
|
}
|
||||||
handler, b := newTestServerHandler(blocks, indexers, db, newClientPeerSet(), clock)
|
handler, b := newTestServerHandler(blocks, indexers, db, clock)
|
||||||
|
|
||||||
var peer *testPeer
|
var peer *testPeer
|
||||||
if newPeer {
|
if newPeer {
|
||||||
|
|
@ -502,7 +506,7 @@ func newServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallba
|
||||||
|
|
||||||
func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, ulcServers []string, ulcFraction int, simClock bool, connect bool, disablePruning bool) (*testServer, *testClient, func()) {
|
func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, ulcServers []string, ulcFraction int, simClock bool, connect bool, disablePruning bool) (*testServer, *testClient, func()) {
|
||||||
sdb, cdb := rawdb.NewMemoryDatabase(), rawdb.NewMemoryDatabase()
|
sdb, cdb := rawdb.NewMemoryDatabase(), rawdb.NewMemoryDatabase()
|
||||||
speers, cpeers := newServerPeerSet(), newClientPeerSet()
|
speers := newServerPeerSet()
|
||||||
|
|
||||||
var clock mclock.Clock = &mclock.System{}
|
var clock mclock.Clock = &mclock.System{}
|
||||||
if simClock {
|
if simClock {
|
||||||
|
|
@ -519,7 +523,7 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexer
|
||||||
ccIndexer, cbIndexer, cbtIndexer := cIndexers[0], cIndexers[1], cIndexers[2]
|
ccIndexer, cbIndexer, cbtIndexer := cIndexers[0], cIndexers[1], cIndexers[2]
|
||||||
odr.SetIndexers(ccIndexer, cbIndexer, cbtIndexer)
|
odr.SetIndexers(ccIndexer, cbIndexer, cbtIndexer)
|
||||||
|
|
||||||
server, b := newTestServerHandler(blocks, sindexers, sdb, cpeers, clock)
|
server, b := newTestServerHandler(blocks, sindexers, sdb, clock)
|
||||||
client := newTestClientHandler(b, odr, cIndexers, cdb, speers, ulcServers, ulcFraction)
|
client := newTestClientHandler(b, odr, cIndexers, cdb, speers, ulcServers, ulcFraction)
|
||||||
|
|
||||||
scIndexer.Start(server.blockchain)
|
scIndexer.Start(server.blockchain)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/clique"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
|
@ -31,7 +31,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -243,26 +242,20 @@ func createMiner(t *testing.T) (*Miner, *event.TypeMux) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("can't create new chain config: %v", err)
|
t.Fatalf("can't create new chain config: %v", err)
|
||||||
}
|
}
|
||||||
// Create event Mux
|
|
||||||
mux := new(event.TypeMux)
|
|
||||||
// Create consensus engine
|
// Create consensus engine
|
||||||
engine := ethash.New(ethash.Config{}, []string{}, false)
|
engine := clique.New(chainConfig.Clique, chainDB)
|
||||||
engine.SetThreads(-1)
|
|
||||||
// Create isLocalBlock
|
|
||||||
isLocalBlock := func(block *types.Block) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Create Ethereum backend
|
// Create Ethereum backend
|
||||||
limit := uint64(1000)
|
bc, err := core.NewBlockChain(chainDB, nil, chainConfig, engine, vm.Config{}, nil, nil)
|
||||||
bc, err := core.NewBlockChain(chainDB, new(core.CacheConfig), chainConfig, engine, vm.Config{}, isLocalBlock, &limit)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("can't create new chain %v", err)
|
t.Fatalf("can't create new chain %v", err)
|
||||||
}
|
}
|
||||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
statedb, _ := state.New(common.Hash{}, state.NewDatabase(chainDB), nil)
|
||||||
blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)}
|
blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)}
|
||||||
|
|
||||||
pool := core.NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
|
pool := core.NewTxPool(testTxPoolConfig, chainConfig, blockchain)
|
||||||
backend := NewMockBackend(bc, pool)
|
backend := NewMockBackend(bc, pool)
|
||||||
|
// Create event Mux
|
||||||
|
mux := new(event.TypeMux)
|
||||||
// Create Miner
|
// Create Miner
|
||||||
return New(backend, &config, chainConfig, mux, engine, isLocalBlock), mux
|
return New(backend, &config, chainConfig, mux, engine, nil), mux
|
||||||
}
|
}
|
||||||
|
|
|
||||||
56
oss-fuzz.sh
Normal file
56
oss-fuzz.sh
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
#/bin/bash -eu
|
||||||
|
# Copyright 2020 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# This file is for integration with Google OSS-Fuzz.
|
||||||
|
# The following ENV variables are available when executing on OSS-fuzz:
|
||||||
|
#
|
||||||
|
# /out/ $OUT Directory to store build artifacts (fuzz targets, dictionaries, options files, seed corpus archives).
|
||||||
|
# /src/ $SRC Directory to checkout source files.
|
||||||
|
# /work/ $WORK Directory to store intermediate files.
|
||||||
|
#
|
||||||
|
# $CC, $CXX, $CCC The C and C++ compiler binaries.
|
||||||
|
# $CFLAGS, $CXXFLAGS C and C++ compiler flags.
|
||||||
|
# $LIB_FUZZING_ENGINE C++ compiler argument to link fuzz target against the prebuilt engine library (e.g. libFuzzer).
|
||||||
|
|
||||||
|
function compile_fuzzer {
|
||||||
|
path=$SRC/go-ethereum/$1
|
||||||
|
func=$2
|
||||||
|
fuzzer=$3
|
||||||
|
echo "Building $fuzzer"
|
||||||
|
(cd $path && \
|
||||||
|
go-fuzz -func $func -o $WORK/$fuzzer.a . && \
|
||||||
|
echo "First stage built OK" && \
|
||||||
|
$CXX $CXXFLAGS $LIB_FUZZING_ENGINE $WORK/$fuzzer.a -o $OUT/$fuzzer && \
|
||||||
|
echo "Second stage built ok" )
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
compile_fuzzer common/bitutil Fuzz fuzzBitutilCompress
|
||||||
|
compile_fuzzer crypto/bn256 FuzzAdd fuzzBn256Add
|
||||||
|
compile_fuzzer crypto/bn256 FuzzMul fuzzBn256Mul
|
||||||
|
compile_fuzzer crypto/bn256 FuzzPair fuzzBn256Pair
|
||||||
|
compile_fuzzer core/vm/runtime Fuzz fuzzVmRuntime
|
||||||
|
compile_fuzzer crypto/blake2b Fuzz fuzzBlake2b
|
||||||
|
compile_fuzzer tests/fuzzers/keystore Fuzz fuzzKeystore
|
||||||
|
compile_fuzzer tests/fuzzers/txfetcher Fuzz fuzzTxfetcher
|
||||||
|
compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp
|
||||||
|
compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie
|
||||||
|
compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie
|
||||||
|
|
||||||
|
# This doesn't work very well @TODO
|
||||||
|
#compile_fuzzertests/fuzzers/abi Fuzz fuzzAbi
|
||||||
|
|
||||||
|
|
@ -184,7 +184,19 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error generating node config: %s", err)
|
return fmt.Errorf("error generating node config: %s", err)
|
||||||
}
|
}
|
||||||
|
// expose the admin namespace via websocket if it's not enabled
|
||||||
|
exposed := confCopy.Stack.WSExposeAll
|
||||||
|
if !exposed {
|
||||||
|
for _, api := range confCopy.Stack.WSModules {
|
||||||
|
if api == "admin" {
|
||||||
|
exposed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exposed {
|
||||||
|
confCopy.Stack.WSModules = append(confCopy.Stack.WSModules, "admin")
|
||||||
|
}
|
||||||
// start the one-shot server that waits for startup information
|
// start the one-shot server that waits for startup information
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
@ -362,13 +374,44 @@ type execNodeConfig struct {
|
||||||
PeerAddrs map[string]string `json:"peer_addrs,omitempty"`
|
PeerAddrs map[string]string `json:"peer_addrs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initLogging() {
|
||||||
|
// Initialize the logging by default first.
|
||||||
|
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat()))
|
||||||
|
glogger.Verbosity(log.LvlInfo)
|
||||||
|
log.Root().SetHandler(glogger)
|
||||||
|
|
||||||
|
confEnv := os.Getenv(envNodeConfig)
|
||||||
|
if confEnv == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var conf execNodeConfig
|
||||||
|
if err := json.Unmarshal([]byte(confEnv), &conf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var writer = os.Stderr
|
||||||
|
if conf.Node.LogFile != "" {
|
||||||
|
logWriter, err := os.Create(conf.Node.LogFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writer = logWriter
|
||||||
|
}
|
||||||
|
var verbosity = log.LvlInfo
|
||||||
|
if conf.Node.LogVerbosity <= log.LvlTrace && conf.Node.LogVerbosity >= log.LvlCrit {
|
||||||
|
verbosity = conf.Node.LogVerbosity
|
||||||
|
}
|
||||||
|
// Reinitialize the logger
|
||||||
|
glogger = log.NewGlogHandler(log.StreamHandler(writer, log.TerminalFormat(true)))
|
||||||
|
glogger.Verbosity(verbosity)
|
||||||
|
log.Root().SetHandler(glogger)
|
||||||
|
}
|
||||||
|
|
||||||
// execP2PNode starts a simulation node when the current binary is executed with
|
// execP2PNode starts a simulation node when the current binary is executed with
|
||||||
// argv[0] being "p2p-node", reading the service / ID from argv[1] / argv[2]
|
// argv[0] being "p2p-node", reading the service / ID from argv[1] / argv[2]
|
||||||
// and the node config from an environment variable.
|
// and the node config from an environment variable.
|
||||||
func execP2PNode() {
|
func execP2PNode() {
|
||||||
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat()))
|
initLogging()
|
||||||
glogger.Verbosity(log.LvlInfo)
|
|
||||||
log.Root().SetHandler(glogger)
|
|
||||||
statusURL := os.Getenv(envStatusURL)
|
statusURL := os.Getenv(envStatusURL)
|
||||||
if statusURL == "" {
|
if statusURL == "" {
|
||||||
log.Crit("missing " + envStatusURL)
|
log.Crit("missing " + envStatusURL)
|
||||||
|
|
@ -380,7 +423,7 @@ func execP2PNode() {
|
||||||
if stackErr != nil {
|
if stackErr != nil {
|
||||||
status.Err = stackErr.Error()
|
status.Err = stackErr.Error()
|
||||||
} else {
|
} else {
|
||||||
status.WSEndpoint = "ws://" + stack.WSEndpoint()
|
status.WSEndpoint = stack.WSEndpoint()
|
||||||
status.NodeInfo = stack.Server().NodeInfo()
|
status.NodeInfo = stack.Server().NodeInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -454,7 +497,6 @@ func startExecNodeStack() (*node.Node, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
services[name] = service
|
services[name] = service
|
||||||
stack.RegisterLifecycle(service)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the snapshot API.
|
// Add the snapshot API.
|
||||||
|
|
|
||||||
|
|
@ -99,8 +99,9 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) {
|
||||||
Dialer: s,
|
Dialer: s,
|
||||||
EnableMsgEvents: config.EnableMsgEvents,
|
EnableMsgEvents: config.EnableMsgEvents,
|
||||||
},
|
},
|
||||||
NoUSB: true,
|
ExternalSigner: config.ExternalSigner,
|
||||||
Logger: log.New("node.id", id.String()),
|
NoUSB: true,
|
||||||
|
Logger: log.New("node.id", id.String()),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -263,7 +264,6 @@ func (sn *SimNode) Start(snapshots map[string][]byte) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sn.running[name] = service
|
sn.running[name] = service
|
||||||
sn.node.RegisterLifecycle(service)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if regErr != nil {
|
if regErr != nil {
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,9 @@ type NodeConfig struct {
|
||||||
// These values need to be checked and acted upon by node Services
|
// These values need to be checked and acted upon by node Services
|
||||||
Properties []string
|
Properties []string
|
||||||
|
|
||||||
|
// ExternalSigner specifies an external URI for a clef-type signer
|
||||||
|
ExternalSigner string
|
||||||
|
|
||||||
// Enode
|
// Enode
|
||||||
node *enode.Node
|
node *enode.Node
|
||||||
|
|
||||||
|
|
@ -117,6 +120,17 @@ type NodeConfig struct {
|
||||||
Reachable func(id enode.ID) bool
|
Reachable func(id enode.ID) bool
|
||||||
|
|
||||||
Port uint16
|
Port uint16
|
||||||
|
|
||||||
|
// LogFile is the log file name of the p2p node at runtime.
|
||||||
|
//
|
||||||
|
// The default value is empty so that the default log writer
|
||||||
|
// is the system standard output.
|
||||||
|
LogFile string
|
||||||
|
|
||||||
|
// LogVerbosity is the log verbosity of the p2p node at runtime.
|
||||||
|
//
|
||||||
|
// The default verbosity is INFO.
|
||||||
|
LogVerbosity log.Lvl
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodeConfigJSON is used to encode and decode NodeConfig as JSON by encoding
|
// nodeConfigJSON is used to encode and decode NodeConfig as JSON by encoding
|
||||||
|
|
@ -125,10 +139,12 @@ type nodeConfigJSON struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
PrivateKey string `json:"private_key"`
|
PrivateKey string `json:"private_key"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Services []string `json:"services"`
|
Lifecycles []string `json:"lifecycles"`
|
||||||
Properties []string `json:"properties"`
|
Properties []string `json:"properties"`
|
||||||
EnableMsgEvents bool `json:"enable_msg_events"`
|
EnableMsgEvents bool `json:"enable_msg_events"`
|
||||||
Port uint16 `json:"port"`
|
Port uint16 `json:"port"`
|
||||||
|
LogFile string `json:"logfile"`
|
||||||
|
LogVerbosity int `json:"log_verbosity"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements the json.Marshaler interface by encoding the config
|
// MarshalJSON implements the json.Marshaler interface by encoding the config
|
||||||
|
|
@ -137,10 +153,12 @@ func (n *NodeConfig) MarshalJSON() ([]byte, error) {
|
||||||
confJSON := nodeConfigJSON{
|
confJSON := nodeConfigJSON{
|
||||||
ID: n.ID.String(),
|
ID: n.ID.String(),
|
||||||
Name: n.Name,
|
Name: n.Name,
|
||||||
Services: n.Lifecycles,
|
Lifecycles: n.Lifecycles,
|
||||||
Properties: n.Properties,
|
Properties: n.Properties,
|
||||||
Port: n.Port,
|
Port: n.Port,
|
||||||
EnableMsgEvents: n.EnableMsgEvents,
|
EnableMsgEvents: n.EnableMsgEvents,
|
||||||
|
LogFile: n.LogFile,
|
||||||
|
LogVerbosity: int(n.LogVerbosity),
|
||||||
}
|
}
|
||||||
if n.PrivateKey != nil {
|
if n.PrivateKey != nil {
|
||||||
confJSON.PrivateKey = hex.EncodeToString(crypto.FromECDSA(n.PrivateKey))
|
confJSON.PrivateKey = hex.EncodeToString(crypto.FromECDSA(n.PrivateKey))
|
||||||
|
|
@ -175,10 +193,12 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
n.Name = confJSON.Name
|
n.Name = confJSON.Name
|
||||||
n.Lifecycles = confJSON.Services
|
n.Lifecycles = confJSON.Lifecycles
|
||||||
n.Properties = confJSON.Properties
|
n.Properties = confJSON.Properties
|
||||||
n.Port = confJSON.Port
|
n.Port = confJSON.Port
|
||||||
n.EnableMsgEvents = confJSON.EnableMsgEvents
|
n.EnableMsgEvents = confJSON.EnableMsgEvents
|
||||||
|
n.LogFile = confJSON.LogFile
|
||||||
|
n.LogVerbosity = log.Lvl(confJSON.LogVerbosity)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -208,6 +228,7 @@ func RandomNodeConfig() *NodeConfig {
|
||||||
Name: fmt.Sprintf("node_%s", enodId.String()),
|
Name: fmt.Sprintf("node_%s", enodId.String()),
|
||||||
Port: port,
|
Port: port,
|
||||||
EnableMsgEvents: true,
|
EnableMsgEvents: true,
|
||||||
|
LogVerbosity: log.LvlInfo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,10 +67,10 @@ var GoerliBootnodes = []string{
|
||||||
"enode://a59e33ccd2b3e52d578f1fbd70c6f9babda2650f0760d6ff3b37742fdcdfdb3defba5d56d315b40c46b70198c7621e63ffa3f987389c7118634b0fefbbdfa7fd@51.15.119.157:40303",
|
"enode://a59e33ccd2b3e52d578f1fbd70c6f9babda2650f0760d6ff3b37742fdcdfdb3defba5d56d315b40c46b70198c7621e63ffa3f987389c7118634b0fefbbdfa7fd@51.15.119.157:40303",
|
||||||
}
|
}
|
||||||
|
|
||||||
// YoloV1Bootnodes are the enode URLs of the P2P bootstrap nodes running on the
|
// YoloV2Bootnodes are the enode URLs of the P2P bootstrap nodes running on the
|
||||||
// YOLOv1 ephemeral test network.
|
// YOLOv2 ephemeral test network.
|
||||||
var YoloV1Bootnodes = []string{
|
var YoloV2Bootnodes = []string{
|
||||||
"enode://9e1096aa59862a6f164994cb5cb16f5124d6c992cdbf4535ff7dea43ea1512afe5448dca9df1b7ab0726129603f1a3336b631e4d7a1a44c94daddd03241587f9@35.178.210.161:30303",
|
"enode://9e1096aa59862a6f164994cb5cb16f5124d6c992cdbf4535ff7dea43ea1512afe5448dca9df1b7ab0726129603f1a3336b631e4d7a1a44c94daddd03241587f9@3.9.20.133:30303",
|
||||||
}
|
}
|
||||||
|
|
||||||
const dnsPrefix = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@"
|
const dnsPrefix = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@"
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ var (
|
||||||
RopstenGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
|
RopstenGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
|
||||||
RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
|
RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
|
||||||
GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a")
|
GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a")
|
||||||
YoloV1GenesisHash = common.HexToHash("0xc3fd235071f24f93865b0850bd2a2119b30f7224d18a0e34c7bbf549ad7e3d36")
|
// TODO: update with yolov2 values
|
||||||
|
YoloV2GenesisHash = common.HexToHash("0x498a7239036dd2cd09e2bb8a80922b78632017958c332b42044c250d603a8a3e")
|
||||||
)
|
)
|
||||||
|
|
||||||
// TrustedCheckpoints associates each known checkpoint with the genesis hash of
|
// TrustedCheckpoints associates each known checkpoint with the genesis hash of
|
||||||
|
|
@ -213,9 +214,9 @@ var (
|
||||||
Threshold: 2,
|
Threshold: 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
// YoloV1ChainConfig contains the chain parameters to run a node on the YOLOv1 test network.
|
// YoloV2ChainConfig contains the chain parameters to run a node on the YOLOv2 test network.
|
||||||
YoloV1ChainConfig = &ChainConfig{
|
YoloV2ChainConfig = &ChainConfig{
|
||||||
ChainID: big.NewInt(133519467574833),
|
ChainID: big.NewInt(133519467574834),
|
||||||
HomesteadBlock: big.NewInt(0),
|
HomesteadBlock: big.NewInt(0),
|
||||||
DAOForkBlock: nil,
|
DAOForkBlock: nil,
|
||||||
DAOForkSupport: true,
|
DAOForkSupport: true,
|
||||||
|
|
@ -227,7 +228,7 @@ var (
|
||||||
PetersburgBlock: big.NewInt(0),
|
PetersburgBlock: big.NewInt(0),
|
||||||
IstanbulBlock: big.NewInt(0),
|
IstanbulBlock: big.NewInt(0),
|
||||||
MuirGlacierBlock: nil,
|
MuirGlacierBlock: nil,
|
||||||
YoloV1Block: big.NewInt(0),
|
YoloV2Block: big.NewInt(0),
|
||||||
Clique: &CliqueConfig{
|
Clique: &CliqueConfig{
|
||||||
Period: 15,
|
Period: 15,
|
||||||
Epoch: 30000,
|
Epoch: 30000,
|
||||||
|
|
@ -320,7 +321,7 @@ type ChainConfig struct {
|
||||||
IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul)
|
IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul)
|
||||||
MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated)
|
MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated)
|
||||||
|
|
||||||
YoloV1Block *big.Int `json:"yoloV1Block,omitempty"` // YOLO v1: https://github.com/ethereum/EIPs/pull/2657 (Ephemeral testnet)
|
YoloV2Block *big.Int `json:"yoloV2Block,omitempty"` // YOLO v2: Gas repricings TODO @holiman add EIP references
|
||||||
EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated)
|
EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated)
|
||||||
|
|
||||||
// Various consensus engines
|
// Various consensus engines
|
||||||
|
|
@ -376,7 +377,7 @@ func (c *ChainConfig) String() string {
|
||||||
default:
|
default:
|
||||||
engine = "unknown"
|
engine = "unknown"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v1: %v, Engine: %v}",
|
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v2: %v, Engine: %v}",
|
||||||
c.ChainID,
|
c.ChainID,
|
||||||
c.HomesteadBlock,
|
c.HomesteadBlock,
|
||||||
c.DAOForkBlock,
|
c.DAOForkBlock,
|
||||||
|
|
@ -389,7 +390,7 @@ func (c *ChainConfig) String() string {
|
||||||
c.PetersburgBlock,
|
c.PetersburgBlock,
|
||||||
c.IstanbulBlock,
|
c.IstanbulBlock,
|
||||||
c.MuirGlacierBlock,
|
c.MuirGlacierBlock,
|
||||||
c.YoloV1Block,
|
c.YoloV2Block,
|
||||||
engine,
|
engine,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -446,9 +447,9 @@ func (c *ChainConfig) IsIstanbul(num *big.Int) bool {
|
||||||
return isForked(c.IstanbulBlock, num)
|
return isForked(c.IstanbulBlock, num)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsYoloV1 returns whether num is either equal to the YoloV1 fork block or greater.
|
// IsYoloV2 returns whether num is either equal to the YoloV1 fork block or greater.
|
||||||
func (c *ChainConfig) IsYoloV1(num *big.Int) bool {
|
func (c *ChainConfig) IsYoloV2(num *big.Int) bool {
|
||||||
return isForked(c.YoloV1Block, num)
|
return isForked(c.YoloV2Block, num)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEWASM returns whether num represents a block number after the EWASM fork
|
// IsEWASM returns whether num represents a block number after the EWASM fork
|
||||||
|
|
@ -494,7 +495,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
|
||||||
{name: "petersburgBlock", block: c.PetersburgBlock},
|
{name: "petersburgBlock", block: c.PetersburgBlock},
|
||||||
{name: "istanbulBlock", block: c.IstanbulBlock},
|
{name: "istanbulBlock", block: c.IstanbulBlock},
|
||||||
{name: "muirGlacierBlock", block: c.MuirGlacierBlock, optional: true},
|
{name: "muirGlacierBlock", block: c.MuirGlacierBlock, optional: true},
|
||||||
{name: "yoloV1Block", block: c.YoloV1Block},
|
{name: "yoloV2Block", block: c.YoloV2Block},
|
||||||
} {
|
} {
|
||||||
if lastFork.name != "" {
|
if lastFork.name != "" {
|
||||||
// Next one must be higher number
|
// Next one must be higher number
|
||||||
|
|
@ -558,8 +559,8 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi
|
||||||
if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, head) {
|
if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, head) {
|
||||||
return newCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock)
|
return newCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock)
|
||||||
}
|
}
|
||||||
if isForkIncompatible(c.YoloV1Block, newcfg.YoloV1Block, head) {
|
if isForkIncompatible(c.YoloV2Block, newcfg.YoloV2Block, head) {
|
||||||
return newCompatError("YOLOv1 fork block", c.YoloV1Block, newcfg.YoloV1Block)
|
return newCompatError("YOLOv2 fork block", c.YoloV2Block, newcfg.YoloV2Block)
|
||||||
}
|
}
|
||||||
if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) {
|
if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) {
|
||||||
return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock)
|
return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock)
|
||||||
|
|
@ -631,7 +632,7 @@ type Rules struct {
|
||||||
ChainID *big.Int
|
ChainID *big.Int
|
||||||
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
|
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
|
||||||
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
|
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
|
||||||
IsYoloV1 bool
|
IsYoloV2 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rules ensures c's ChainID is not nil.
|
// Rules ensures c's ChainID is not nil.
|
||||||
|
|
@ -650,6 +651,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules {
|
||||||
IsConstantinople: c.IsConstantinople(num),
|
IsConstantinople: c.IsConstantinople(num),
|
||||||
IsPetersburg: c.IsPetersburg(num),
|
IsPetersburg: c.IsPetersburg(num),
|
||||||
IsIstanbul: c.IsIstanbul(num),
|
IsIstanbul: c.IsIstanbul(num),
|
||||||
IsYoloV1: c.IsYoloV1(num),
|
IsYoloV2: c.IsYoloV2(num),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
const (
|
const (
|
||||||
VersionMajor = 1 // Major version component of the current release
|
VersionMajor = 1 // Major version component of the current release
|
||||||
VersionMinor = 9 // Minor version component of the current release
|
VersionMinor = 9 // Minor version component of the current release
|
||||||
VersionPatch = 23 // Patch version component of the current release
|
VersionPatch = 24 // Patch version component of the current release
|
||||||
VersionMeta = "stable" // Version metadata to append to the version string
|
VersionMeta = "stable" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
71
rlp/raw.go
71
rlp/raw.go
|
|
@ -180,3 +180,74 @@ func readSize(b []byte, slen byte) (uint64, error) {
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppendUint64 appends the RLP encoding of i to b, and returns the resulting slice.
|
||||||
|
func AppendUint64(b []byte, i uint64) []byte {
|
||||||
|
if i == 0 {
|
||||||
|
return append(b, 0x80)
|
||||||
|
} else if i < 128 {
|
||||||
|
return append(b, byte(i))
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case i < (1 << 8):
|
||||||
|
return append(b, 0x81, byte(i))
|
||||||
|
case i < (1 << 16):
|
||||||
|
return append(b, 0x82,
|
||||||
|
byte(i>>8),
|
||||||
|
byte(i),
|
||||||
|
)
|
||||||
|
case i < (1 << 24):
|
||||||
|
return append(b, 0x83,
|
||||||
|
byte(i>>16),
|
||||||
|
byte(i>>8),
|
||||||
|
byte(i),
|
||||||
|
)
|
||||||
|
case i < (1 << 32):
|
||||||
|
return append(b, 0x84,
|
||||||
|
byte(i>>24),
|
||||||
|
byte(i>>16),
|
||||||
|
byte(i>>8),
|
||||||
|
byte(i),
|
||||||
|
)
|
||||||
|
case i < (1 << 40):
|
||||||
|
return append(b, 0x85,
|
||||||
|
byte(i>>32),
|
||||||
|
byte(i>>24),
|
||||||
|
byte(i>>16),
|
||||||
|
byte(i>>8),
|
||||||
|
byte(i),
|
||||||
|
)
|
||||||
|
|
||||||
|
case i < (1 << 48):
|
||||||
|
return append(b, 0x86,
|
||||||
|
byte(i>>40),
|
||||||
|
byte(i>>32),
|
||||||
|
byte(i>>24),
|
||||||
|
byte(i>>16),
|
||||||
|
byte(i>>8),
|
||||||
|
byte(i),
|
||||||
|
)
|
||||||
|
case i < (1 << 56):
|
||||||
|
return append(b, 0x87,
|
||||||
|
byte(i>>48),
|
||||||
|
byte(i>>40),
|
||||||
|
byte(i>>32),
|
||||||
|
byte(i>>24),
|
||||||
|
byte(i>>16),
|
||||||
|
byte(i>>8),
|
||||||
|
byte(i),
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return append(b, 0x88,
|
||||||
|
byte(i>>56),
|
||||||
|
byte(i>>48),
|
||||||
|
byte(i>>40),
|
||||||
|
byte(i>>32),
|
||||||
|
byte(i>>24),
|
||||||
|
byte(i>>16),
|
||||||
|
byte(i>>8),
|
||||||
|
byte(i),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"testing/quick"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCountValues(t *testing.T) {
|
func TestCountValues(t *testing.T) {
|
||||||
|
|
@ -239,3 +240,40 @@ func TestReadSize(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAppendUint64(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input uint64
|
||||||
|
slice []byte
|
||||||
|
output string
|
||||||
|
}{
|
||||||
|
{0, nil, "80"},
|
||||||
|
{1, nil, "01"},
|
||||||
|
{2, nil, "02"},
|
||||||
|
{127, nil, "7F"},
|
||||||
|
{128, nil, "8180"},
|
||||||
|
{129, nil, "8181"},
|
||||||
|
{0xFFFFFF, nil, "83FFFFFF"},
|
||||||
|
{127, []byte{1, 2, 3}, "0102037F"},
|
||||||
|
{0xFFFFFF, []byte{1, 2, 3}, "01020383FFFFFF"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
x := AppendUint64(test.slice, test.input)
|
||||||
|
if !bytes.Equal(x, unhex(test.output)) {
|
||||||
|
t.Errorf("AppendUint64(%v, %d): got %x, want %s", test.slice, test.input, x, test.output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendUint64Random(t *testing.T) {
|
||||||
|
fn := func(i uint64) bool {
|
||||||
|
enc, _ := EncodeToBytes(i)
|
||||||
|
encAppend := AppendUint64(nil, i)
|
||||||
|
return bytes.Equal(enc, encAppend)
|
||||||
|
}
|
||||||
|
config := quick.Config{MaxCountScale: 50}
|
||||||
|
if err := quick.Check(fn, &config); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
23
tests/fuzzers/stacktrie/debug/main.go
Normal file
23
tests/fuzzers/stacktrie/debug/main.go
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/tests/fuzzers/stacktrie"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: debug <file>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
crasher := os.Args[1]
|
||||||
|
data, err := ioutil.ReadFile(crasher)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
stacktrie.Debug(data)
|
||||||
|
}
|
||||||
197
tests/fuzzers/stacktrie/trie_fuzzer.go
Normal file
197
tests/fuzzers/stacktrie/trie_fuzzer.go
Normal file
|
|
@ -0,0 +1,197 @@
|
||||||
|
// Copyright 2020 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 stacktrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fuzzer struct {
|
||||||
|
input io.Reader
|
||||||
|
exhausted bool
|
||||||
|
debugging bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fuzzer) read(size int) []byte {
|
||||||
|
out := make([]byte, size)
|
||||||
|
if _, err := f.input.Read(out); err != nil {
|
||||||
|
f.exhausted = true
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fuzzer) readSlice(min, max int) []byte {
|
||||||
|
var a uint16
|
||||||
|
binary.Read(f.input, binary.LittleEndian, &a)
|
||||||
|
size := min + int(a)%(max-min)
|
||||||
|
out := make([]byte, size)
|
||||||
|
if _, err := f.input.Read(out); err != nil {
|
||||||
|
f.exhausted = true
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// spongeDb is a dummy db backend which accumulates writes in a sponge
|
||||||
|
type spongeDb struct {
|
||||||
|
sponge hash.Hash
|
||||||
|
debug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *spongeDb) Has(key []byte) (bool, error) { panic("implement me") }
|
||||||
|
func (s *spongeDb) Get(key []byte) ([]byte, error) { return nil, errors.New("no such elem") }
|
||||||
|
func (s *spongeDb) Delete(key []byte) error { panic("implement me") }
|
||||||
|
func (s *spongeDb) NewBatch() ethdb.Batch { return &spongeBatch{s} }
|
||||||
|
func (s *spongeDb) Stat(property string) (string, error) { panic("implement me") }
|
||||||
|
func (s *spongeDb) Compact(start []byte, limit []byte) error { panic("implement me") }
|
||||||
|
func (s *spongeDb) Close() error { return nil }
|
||||||
|
|
||||||
|
func (s *spongeDb) Put(key []byte, value []byte) error {
|
||||||
|
if s.debug {
|
||||||
|
fmt.Printf("db.Put %x : %x\n", key, value)
|
||||||
|
}
|
||||||
|
s.sponge.Write(key)
|
||||||
|
s.sponge.Write(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *spongeDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { panic("implement me") }
|
||||||
|
|
||||||
|
// spongeBatch is a dummy batch which immediately writes to the underlying spongedb
|
||||||
|
type spongeBatch struct {
|
||||||
|
db *spongeDb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *spongeBatch) Put(key, value []byte) error {
|
||||||
|
b.db.Put(key, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (b *spongeBatch) Delete(key []byte) error { panic("implement me") }
|
||||||
|
func (b *spongeBatch) ValueSize() int { return 100 }
|
||||||
|
func (b *spongeBatch) Write() error { return nil }
|
||||||
|
func (b *spongeBatch) Reset() {}
|
||||||
|
func (b *spongeBatch) Replay(w ethdb.KeyValueWriter) error { return nil }
|
||||||
|
|
||||||
|
type kv struct {
|
||||||
|
k, v []byte
|
||||||
|
}
|
||||||
|
type kvs []kv
|
||||||
|
|
||||||
|
func (k kvs) Len() int {
|
||||||
|
return len(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k kvs) Less(i, j int) bool {
|
||||||
|
return bytes.Compare(k[i].k, k[j].k) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k kvs) Swap(i, j int) {
|
||||||
|
k[j], k[i] = k[i], k[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
// The function must return
|
||||||
|
// 1 if the fuzzer should increase priority of the
|
||||||
|
// given input during subsequent fuzzing (for example, the input is lexically
|
||||||
|
// correct and was parsed successfully);
|
||||||
|
// -1 if the input must not be added to corpus even if gives new coverage; and
|
||||||
|
// 0 otherwise
|
||||||
|
// other values are reserved for future use.
|
||||||
|
func Fuzz(data []byte) int {
|
||||||
|
f := fuzzer{
|
||||||
|
input: bytes.NewReader(data),
|
||||||
|
exhausted: false,
|
||||||
|
}
|
||||||
|
return f.fuzz()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debug(data []byte) int {
|
||||||
|
f := fuzzer{
|
||||||
|
input: bytes.NewReader(data),
|
||||||
|
exhausted: false,
|
||||||
|
debugging: true,
|
||||||
|
}
|
||||||
|
return f.fuzz()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fuzzer) fuzz() int {
|
||||||
|
|
||||||
|
// This spongeDb is used to check the sequence of disk-db-writes
|
||||||
|
var (
|
||||||
|
spongeA = &spongeDb{sponge: sha3.NewLegacyKeccak256()}
|
||||||
|
dbA = trie.NewDatabase(spongeA)
|
||||||
|
trieA, _ = trie.New(common.Hash{}, dbA)
|
||||||
|
spongeB = &spongeDb{sponge: sha3.NewLegacyKeccak256()}
|
||||||
|
trieB = trie.NewStackTrie(spongeB)
|
||||||
|
vals kvs
|
||||||
|
useful bool
|
||||||
|
maxElements = 10000
|
||||||
|
)
|
||||||
|
// Fill the trie with elements
|
||||||
|
for i := 0; !f.exhausted && i < maxElements; i++ {
|
||||||
|
k := f.read(32)
|
||||||
|
v := f.readSlice(1, 500)
|
||||||
|
if f.exhausted {
|
||||||
|
// If it was exhausted while reading, the value may be all zeroes,
|
||||||
|
// thus 'deletion' which is not supported on stacktrie
|
||||||
|
break
|
||||||
|
}
|
||||||
|
vals = append(vals, kv{k: k, v: v})
|
||||||
|
trieA.Update(k, v)
|
||||||
|
useful = true
|
||||||
|
}
|
||||||
|
if !useful {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
// Flush trie -> database
|
||||||
|
rootA, err := trieA.Commit(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// Flush memdb -> disk (sponge)
|
||||||
|
dbA.Commit(rootA, false, nil)
|
||||||
|
|
||||||
|
// Stacktrie requires sorted insertion
|
||||||
|
sort.Sort(vals)
|
||||||
|
for _, kv := range vals {
|
||||||
|
if f.debugging {
|
||||||
|
fmt.Printf("{\"0x%x\" , \"0x%x\"} // stacktrie.Update\n", kv.k, kv.v)
|
||||||
|
}
|
||||||
|
trieB.Update(kv.k, kv.v)
|
||||||
|
}
|
||||||
|
rootB := trieB.Hash()
|
||||||
|
if _, err := trieB.Commit(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if rootA != rootB {
|
||||||
|
panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootB))
|
||||||
|
}
|
||||||
|
sumA := spongeA.sponge.Sum(nil)
|
||||||
|
sumB := spongeB.sponge.Sum(nil)
|
||||||
|
if !bytes.Equal(sumA, sumB) {
|
||||||
|
panic(fmt.Sprintf("sequence differ: (trie) %x != %x (stacktrie)", sumA, sumB))
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
@ -122,15 +122,22 @@ func Generate(input []byte) randTest {
|
||||||
return steps
|
return steps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The function must return
|
||||||
|
// 1 if the fuzzer should increase priority of the
|
||||||
|
// given input during subsequent fuzzing (for example, the input is lexically
|
||||||
|
// correct and was parsed successfully);
|
||||||
|
// -1 if the input must not be added to corpus even if gives new coverage; and
|
||||||
|
// 0 otherwise
|
||||||
|
// other values are reserved for future use.
|
||||||
func Fuzz(input []byte) int {
|
func Fuzz(input []byte) int {
|
||||||
program := Generate(input)
|
program := Generate(input)
|
||||||
if len(program) == 0 {
|
if len(program) == 0 {
|
||||||
return -1
|
return 0
|
||||||
}
|
}
|
||||||
if err := runRandTest(program); err != nil {
|
if err := runRandTest(program); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return 0
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRandTest(rt randTest) error {
|
func runRandTest(rt randTest) error {
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ var Forks = map[string]*params.ChainConfig{
|
||||||
PetersburgBlock: big.NewInt(0),
|
PetersburgBlock: big.NewInt(0),
|
||||||
IstanbulBlock: big.NewInt(5),
|
IstanbulBlock: big.NewInt(5),
|
||||||
},
|
},
|
||||||
"YOLOv1": {
|
"YOLOv2": {
|
||||||
ChainID: big.NewInt(1),
|
ChainID: big.NewInt(1),
|
||||||
HomesteadBlock: big.NewInt(0),
|
HomesteadBlock: big.NewInt(0),
|
||||||
EIP150Block: big.NewInt(0),
|
EIP150Block: big.NewInt(0),
|
||||||
|
|
@ -151,9 +151,9 @@ var Forks = map[string]*params.ChainConfig{
|
||||||
ConstantinopleBlock: big.NewInt(0),
|
ConstantinopleBlock: big.NewInt(0),
|
||||||
PetersburgBlock: big.NewInt(0),
|
PetersburgBlock: big.NewInt(0),
|
||||||
IstanbulBlock: big.NewInt(0),
|
IstanbulBlock: big.NewInt(0),
|
||||||
YoloV1Block: big.NewInt(0),
|
YoloV2Block: big.NewInt(0),
|
||||||
},
|
},
|
||||||
// This specification is subject to change, but is for now identical to YOLOv1
|
// This specification is subject to change, but is for now identical to YOLOv2
|
||||||
// for cross-client testing purposes
|
// for cross-client testing purposes
|
||||||
"Berlin": {
|
"Berlin": {
|
||||||
ChainID: big.NewInt(1),
|
ChainID: big.NewInt(1),
|
||||||
|
|
@ -165,7 +165,7 @@ var Forks = map[string]*params.ChainConfig{
|
||||||
ConstantinopleBlock: big.NewInt(0),
|
ConstantinopleBlock: big.NewInt(0),
|
||||||
PetersburgBlock: big.NewInt(0),
|
PetersburgBlock: big.NewInt(0),
|
||||||
IstanbulBlock: big.NewInt(0),
|
IstanbulBlock: big.NewInt(0),
|
||||||
YoloV1Block: big.NewInt(0),
|
YoloV2Block: big.NewInt(0),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,16 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
|
||||||
context.GetHash = vmTestBlockHash
|
context.GetHash = vmTestBlockHash
|
||||||
evm := vm.NewEVM(context, statedb, config, vmconfig)
|
evm := vm.NewEVM(context, statedb, config, vmconfig)
|
||||||
|
|
||||||
|
if config.IsYoloV2(context.BlockNumber) {
|
||||||
|
statedb.AddAddressToAccessList(msg.From())
|
||||||
|
if dst := msg.To(); dst != nil {
|
||||||
|
statedb.AddAddressToAccessList(*dst)
|
||||||
|
// If it's a create-tx, the destination will be added inside evm.create
|
||||||
|
}
|
||||||
|
for _, addr := range evm.ActivePrecompiles() {
|
||||||
|
statedb.AddAddressToAccessList(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
gaspool := new(core.GasPool)
|
gaspool := new(core.GasPool)
|
||||||
gaspool.AddGas(block.GasLimit())
|
gaspool.AddGas(block.GasLimit())
|
||||||
snapshot := statedb.Snapshot()
|
snapshot := statedb.Snapshot()
|
||||||
|
|
@ -225,7 +235,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo
|
||||||
|
|
||||||
var snaps *snapshot.Tree
|
var snaps *snapshot.Tree
|
||||||
if snapshotter {
|
if snapshotter {
|
||||||
snaps = snapshot.New(db, sdb.TrieDB(), 1, root, false)
|
snaps = snapshot.New(db, sdb.TrieDB(), 1, root, false, false)
|
||||||
}
|
}
|
||||||
statedb, _ = state.New(root, sdb, snaps)
|
statedb, _ = state.New(root, sdb, snaps)
|
||||||
return snaps, statedb
|
return snaps, statedb
|
||||||
|
|
|
||||||
|
|
@ -314,19 +314,22 @@ func (st *StackTrie) hash() {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
case extNode:
|
case extNode:
|
||||||
|
st.children[0].hash()
|
||||||
h = newHasher(false)
|
h = newHasher(false)
|
||||||
defer returnHasherToPool(h)
|
defer returnHasherToPool(h)
|
||||||
h.tmp.Reset()
|
h.tmp.Reset()
|
||||||
st.children[0].hash()
|
var valuenode node
|
||||||
// This is also possible:
|
if len(st.children[0].val) < 32 {
|
||||||
//sz := hexToCompactInPlace(st.key)
|
valuenode = rawNode(st.children[0].val)
|
||||||
//n := [][]byte{
|
} else {
|
||||||
// st.key[:sz],
|
valuenode = hashNode(st.children[0].val)
|
||||||
// st.children[0].val,
|
}
|
||||||
//}
|
n := struct {
|
||||||
n := [][]byte{
|
Key []byte
|
||||||
hexToCompact(st.key),
|
Val node
|
||||||
st.children[0].val,
|
}{
|
||||||
|
Key: hexToCompact(st.key),
|
||||||
|
Val: valuenode,
|
||||||
}
|
}
|
||||||
if err := rlp.Encode(&h.tmp, n); err != nil {
|
if err := rlp.Encode(&h.tmp, n); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
@ -406,6 +409,18 @@ func (st *StackTrie) Commit() (common.Hash, error) {
|
||||||
return common.Hash{}, ErrCommitDisabled
|
return common.Hash{}, ErrCommitDisabled
|
||||||
}
|
}
|
||||||
st.hash()
|
st.hash()
|
||||||
h := common.BytesToHash(st.val)
|
if len(st.val) != 32 {
|
||||||
return h, nil
|
// If the node's RLP isn't 32 bytes long, the node will not
|
||||||
|
// be hashed (and committed), and instead contain the rlp-encoding of the
|
||||||
|
// node. For the top level node, we need to force the hashing+commit.
|
||||||
|
ret := make([]byte, 32)
|
||||||
|
h := newHasher(false)
|
||||||
|
defer returnHasherToPool(h)
|
||||||
|
h.sha.Reset()
|
||||||
|
h.sha.Write(st.val)
|
||||||
|
h.sha.Read(ret)
|
||||||
|
st.db.Put(ret, st.val)
|
||||||
|
return common.BytesToHash(ret), nil
|
||||||
|
}
|
||||||
|
return common.BytesToHash(st.val), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -240,3 +240,52 @@ func TestDerivableList(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestUpdateSmallNodes tests a case where the leaves are small (both key and value),
|
||||||
|
// which causes a lot of node-within-node. This case was found via fuzzing.
|
||||||
|
func TestUpdateSmallNodes(t *testing.T) {
|
||||||
|
st := NewStackTrie(nil)
|
||||||
|
nt, _ := New(common.Hash{}, NewDatabase(memorydb.New()))
|
||||||
|
kvs := []struct {
|
||||||
|
K string
|
||||||
|
V string
|
||||||
|
}{
|
||||||
|
{"63303030", "3041"}, // stacktrie.Update
|
||||||
|
{"65", "3000"}, // stacktrie.Update
|
||||||
|
}
|
||||||
|
for _, kv := range kvs {
|
||||||
|
nt.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V))
|
||||||
|
st.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V))
|
||||||
|
}
|
||||||
|
if nt.Hash() != st.Hash() {
|
||||||
|
t.Fatalf("error %x != %x", st.Hash(), nt.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUpdateVariableKeys contains a case which stacktrie fails: when keys of different
|
||||||
|
// sizes are used, and the second one has the same prefix as the first, then the
|
||||||
|
// stacktrie fails, since it's unable to 'expand' on an already added leaf.
|
||||||
|
// For all practical purposes, this is fine, since keys are fixed-size length
|
||||||
|
// in account and storage tries.
|
||||||
|
//
|
||||||
|
// The test is marked as 'skipped', and exists just to have the behaviour documented.
|
||||||
|
// This case was found via fuzzing.
|
||||||
|
func TestUpdateVariableKeys(t *testing.T) {
|
||||||
|
t.SkipNow()
|
||||||
|
st := NewStackTrie(nil)
|
||||||
|
nt, _ := New(common.Hash{}, NewDatabase(memorydb.New()))
|
||||||
|
kvs := []struct {
|
||||||
|
K string
|
||||||
|
V string
|
||||||
|
}{
|
||||||
|
{"0x33303534636532393561313031676174", "303030"},
|
||||||
|
{"0x3330353463653239356131303167617430", "313131"},
|
||||||
|
}
|
||||||
|
for _, kv := range kvs {
|
||||||
|
nt.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V))
|
||||||
|
st.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V))
|
||||||
|
}
|
||||||
|
if nt.Hash() != st.Hash() {
|
||||||
|
t.Fatalf("error %x != %x", st.Hash(), nt.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -853,6 +853,42 @@ func TestCommitSequenceStackTrie(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCommitSequenceSmallRoot tests that a trie which is essentially only a
|
||||||
|
// small (<32 byte) shortnode with an included value is properly committed to a
|
||||||
|
// database.
|
||||||
|
// This case might not matter, since in practice, all keys are 32 bytes, which means
|
||||||
|
// that even a small trie which contains a leaf will have an extension making it
|
||||||
|
// not fit into 32 bytes, rlp-encoded. However, it's still the correct thing to do.
|
||||||
|
func TestCommitSequenceSmallRoot(t *testing.T) {
|
||||||
|
s := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "a"}
|
||||||
|
db := NewDatabase(s)
|
||||||
|
trie, _ := New(common.Hash{}, db)
|
||||||
|
// Another sponge is used for the stacktrie commits
|
||||||
|
stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"}
|
||||||
|
stTrie := NewStackTrie(stackTrieSponge)
|
||||||
|
// Add a single small-element to the trie(s)
|
||||||
|
key := make([]byte, 5)
|
||||||
|
key[0] = 1
|
||||||
|
trie.TryUpdate(key, []byte{0x1})
|
||||||
|
stTrie.TryUpdate(key, []byte{0x1})
|
||||||
|
// Flush trie -> database
|
||||||
|
root, _ := trie.Commit(nil)
|
||||||
|
// Flush memdb -> disk (sponge)
|
||||||
|
db.Commit(root, false, nil)
|
||||||
|
// And flush stacktrie -> disk
|
||||||
|
stRoot, err := stTrie.Commit()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to commit stack trie %v", err)
|
||||||
|
}
|
||||||
|
if stRoot != root {
|
||||||
|
t.Fatalf("root wrong, got %x exp %x", stRoot, root)
|
||||||
|
}
|
||||||
|
fmt.Printf("root: %x\n", stRoot)
|
||||||
|
if got, exp := stackTrieSponge.sponge.Sum(nil), s.sponge.Sum(nil); !bytes.Equal(got, exp) {
|
||||||
|
t.Fatalf("test, disk write sequence wrong:\ngot %x exp %x\n", got, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// BenchmarkCommitAfterHashFixedSize benchmarks the Commit (after Hash) of a fixed number of updates to a trie.
|
// BenchmarkCommitAfterHashFixedSize benchmarks the Commit (after Hash) of a fixed number of updates to a trie.
|
||||||
// This benchmark is meant to capture the difference on efficiency of small versus large changes. Typically,
|
// This benchmark is meant to capture the difference on efficiency of small versus large changes. Typically,
|
||||||
// storage tries are small (a couple of entries), whereas the full post-block account trie update is large (a couple
|
// storage tries are small (a couple of entries), whereas the full post-block account trie update is large (a couple
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue