Merge branch 'geth-bor' into geth-bor-changes

This commit is contained in:
Jaynti Kanani 2020-11-20 10:53:35 +05:30
commit c241e81ea3
No known key found for this signature in database
GPG key ID: 4396982C976BAE5E
97 changed files with 4805 additions and 1034 deletions

11
.github/CODEOWNERS vendored
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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])
} }

View file

@ -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

View file

@ -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

View file

@ -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")
} }
} }

View file

@ -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

View file

@ -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.

View file

@ -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 {

View file

@ -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
} }

View file

@ -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)
} }

View file

@ -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])

Binary file not shown.

View file

@ -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"
} }

Binary file not shown.

View file

@ -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))
} }
} }
} }

View file

@ -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
View 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
}

View file

@ -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)

View file

@ -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,
}, },

View file

@ -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)

View file

@ -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()

View file

@ -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,

View file

@ -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,

View file

@ -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)

View file

@ -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")
} }

View file

@ -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".

View file

@ -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)

View file

@ -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, ""

View file

@ -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
} }
} }

View file

@ -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 {

View file

@ -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)

View file

@ -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)

View 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())
}
}

View file

@ -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),

View file

@ -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)
}
}

View file

@ -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)
} }

View file

@ -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)
}

View file

@ -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
View 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)
}

View file

@ -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
}

View file

@ -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.

View file

@ -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,

View file

@ -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
} }

View file

@ -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()
}

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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 {

View file

@ -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()
} }

View file

@ -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,

View file

@ -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{

View file

@ -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
}

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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
} }

View file

@ -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
View 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
}

View file

@ -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,

View file

@ -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)
}
}

View file

@ -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 {

View file

@ -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()

View file

@ -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()
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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() {

View file

@ -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())
}
}

View file

@ -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
} }

View file

@ -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)
} }
} }
} }

View file

@ -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))

View file

@ -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{})

View file

@ -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

View file

@ -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 {

View file

@ -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

View file

@ -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)
}
}

View file

@ -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}
}
}
}

View file

@ -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)

View file

@ -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
View 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

View file

@ -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.

View file

@ -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 {

View file

@ -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,
} }
} }

View file

@ -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@"

View file

@ -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),
} }
} }

View file

@ -23,7 +23,7 @@ import (
const ( const (
VersionMajor = 1 // Major version component of the current release VersionMajor = 1 // Major version component of the current release
VersionMinor = 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
) )

View file

@ -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),
)
}
}

View file

@ -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)
}
}

View 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)
}

View 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
}

View file

@ -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 {

View file

@ -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),
}, },
} }

View file

@ -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

View file

@ -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
} }

View file

@ -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())
}
}

View file

@ -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